Аўтаправізіянінг Yealink T19 + дынамічная адрасная кніга

Калі я прыйшоў працаваць у гэтую кампанію, у мяне ўжо мелася некаторая база па ip апаратах, некалькім серверам з asterisk і нашлепкай у выглядзе 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

Тут мы выкарыстоўваем функцыю sniff з фраемворка scapy, з дапамогай яе мы атрымліваем загадзя вызначаны 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

Высвятляем пароль для дадзенага акаўнта. Для гэтага звяртаемся да табліцы asterisk.sip і ў ёй да поля data.

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 сервера спампоўваецца template канфігурацыя, у якую мы ўносім свае змены і захоўваем з як mac.cfg. Гэта значыць для Yealink існуюць два выгляду канфігурацыі, адна глабальная, а другая ўжываецца да пэўнага тэлефона і павінна быць выгляду mac_тэлефона.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.

PS: Для мацнейшай маштабаванасці можна вынесці асноўныя налады (зменныя) у асобны файлік.

Крыніца: habr.com

Дадаць каментар