Автоматтык камсыздоо Yealink T19 + динамикалык дарек китеби

Мен бул компанияга иштегени келгенде, менде IP түзмөктөрдүн маалымат базасы, жылдызчалуу бир нече серверлер жана FreeBPX түрүндөгү патч бар болчу. Мындан тышкары, аналогдук АТС Samsung IDCS500 параллелдүү иштеген жана жалпысынан компаниянын негизги байланыш системасы болгон, IP телефония сатуу бөлүмү үчүн гана иштеген. Анан баары ушинтип бышырып кете бермек, бирок жакшы күндөрдүн биринде бардыгын IP телефонияга өткөрүү боюнча токтом чыгып, мөөнөттөр макулдашылып, жабдуулар сатылып алынып, ишкананы 21-кылымга өткөрүү планы ишке аша баштады.
Мындай кырдаалда тынчсыздана баштаган биринчи нерсе, кандайдыр бир жол менен башкаруу керек болгон телефондук аппараттардын тездик менен өсүп жатышы, экинчиси абдан тынчсыздандырган нерсе - телефон китепчеси. Эгерде Endpoint Manager бизге биринчисинде жардам бере алса (ал, айтмакчы, FreePBXтин акыркы версияларынан алынып салынган), анда китепте бир нече суроолор пайда болгон:

  • Биринчиден, колдонуучулардын орду/суюктугу дайыма өзгөрүп турганда анын тактыгын кантип камсыз кылуу керек?
  • Экинчиден, телефондорду кантип толугу менен жекелештирүү керек. Жана ар дайым байланыштын атын толтурбайсызбы?

Маселе кызыктуу болду, чечим көпкө созулган жок. Эми толук тизмени берем, анан ирети менен карайбыз.

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

Программа колдонуучунун компьютеринде иштейт жана компьютер тармакка телефон аркылуу туташкан шартта иштейт, анткени Yealink T19 шлюз катары иштей албайт.

Биринчиден, биз түшүнүшүбүз керек, ал туташканбы? жана биздин телефондо кандай Mac жана IP бар.

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

Бул жерде биз scapy алкагынан sniff функциясын колдонобуз, анын жардамы менен биз алдын ала аныкталган udp пакетин алабыз, 70 секунд күтөбүз жана эч нерсе кармабасак, чыгабыз.

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

Андан кийин, биз аппарат чындап эле Yealink экенин текшерип, керектүү маанилерди (ip жана mac) кайтарабыз.

Атайын суроону колдонуу менен биз телефондогу учурдагы эсепти табабыз. Бул үчүн, учурдагы конфигурация телефондон жүктөлүп алынат жана талданат.

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

Бул эсептин сырсөзүн табыңыз. Бул үчүн, биз жылдызча.sip таблицасына жана андагы маалымат талаасына кайрылабыз.

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

Ооба, акыркы этап үчүн биз ldap AD менен туташабыз жана функция аркылуу алынган sAMAccountName аркылуу getpass.getuser() учурдагы колдонуучунун cn кодун алыңыз (көбүнчө колдонуучунун толук атын камтыйт).

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

Биз маалымат базасында алдын ала түзүлгөн таблицага туташабыз (мен аны ошол жерде түздүм) жана үйрөнгөнүбүздүн бардыгын киргизебиз, атап айтканда: ip, mac, колдонуучунун аты.

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

Биз бул жерден токтосок болот, анткени биз динамикалык дарек китепчесин түзүп койгонбуз, сиз сурасаңыз болот, бирок мен андан ары бардым жана бул жерге түзмөктөрдүн авто-провизиясын коштум.

Бул үчүн, шаблон конфигурациясы алдын ала конфигурацияланган tftp серверинен жүктөлүп алынат, ага биз өзгөртүүлөрдү киргизип, аны mac.cfg катары сактайбыз. Башкача айтканда, Yealink үчүн конфигурациянын эки түрү бар, бири глобалдуу, экинчиси белгилүү бир телефонго тиешелүү жана mac_phone.cfg формасында болушу керек.

Файлдагы бардык өзгөртүүлөрдөн жана аны кайра tftp серверине сактап койгондон кийин, телефонго аппаратты камсыздоо жана кайра жүктөө буйругун беребиз.

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

Түзмөктү кайра жүктөгөндөн кийин, телефондун экранында толук аты-жөнүн алабыз + маалымат базасы түрүндө ар дайым туура толтурулган дарек китеби, андан кийин мазмунду динамикалык көрсөтүү үчүн XML жана бир аз PHP кошуу гана калды. Мындай мисалдар көп, атүгүл YEALINK өзүндө да бар.

P.S.: Кеңири масштабдалуу үчүн сиз негизги орнотууларды (өзгөрмөлөрдү) өзүнчө файлга салсаңыз болот.

Source: www.habr.com

Комментарий кошуу