Kui ma sellesse ettevõttesse tööle tulin, oli mul juba IP-seadmete andmebaas, mitu tärniga serverit ja plaaster FreeBPX kujul. Lisaks töötas paralleelselt analoog-PBX Samsung IDCS500, mis oli üldiselt ettevõtte peamine sidesüsteem, IP-telefon töötas ainult müügiosakonnas. Ja kõik oleks niimoodi edasi küpsetatud, kuid ühel ilusal päeval anti käsk kõik IP-telefonile üle viia, lepiti kokku tähtajad, osteti seadmed ja hakati ellu viima plaani viia ettevõte 21. sajandisse.
Esimese asjana hakkab sellises olukorras muret tegema kiiresti kasvav telefoniaparaatide hulk, mida tuleb kuidagi majandada, teiseks tegi väga murelikuks telefoniraamat. Kui Endpoint Manager sai meid aidata esimesega (mis muide oli FreePBX-i viimastest versioonidest välja lõigatud), siis raamatuga tekkisid mõned küsimused:
- Esiteks, kuidas tagada selle täpsus, kui kasutajate asukoht/voolavus pidevalt muutub?
- Teiseks, kuidas telefone täielikult depersonaliseerida. Ja mitte iga kord kontakti nime täita?
Probleem oli huvitav, lahendus ei võtnud kaua aega. Nüüd annan täieliku nimekirja ja siis vaatame seda järjekorras.
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')
Programm töötab kasutaja arvutis ja töötab eeldusel, et arvuti on telefoni kaudu võrku ühendatud, kuna Yealink T19 ei saa lüüsina töötada.
Esiteks peame mõistma, kas see on ühendatud? ja mis mac ja ip meie telefonil on.
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
Siin kasutame scapy raamistiku funktsiooni sniff, mille abil saame etteantud udp paketi, ootame 70 sekundit ja kui midagi ei taba, siis väljume.
count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060"
Järgmisena veendume, et seade on tõepoolest Yealink, ja tagastame vajalikud väärtused (ip ja mac).
Erisoovi kasutades selgitame välja arvelduskonto telefonis. Selleks laaditakse praegune konfiguratsioon telefonist alla ja sõelutakse.
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
Uurige selle konto parooli. Selleks pöördume tabeli asterisk.sip ja selles oleva andmevälja poole.
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
Noh, viimases etapis loome ühenduse ldap AD-ga ja kasutades funktsiooni kaudu saadud sAMAccountName getpass.getuser() võtke praeguse kasutaja cn (mis sisaldab tavaliselt kasutaja täisnime).
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
Ühendame andmebaasis eelnevalt loodud tabeliga (mina lõin selle seal) ja sisestame kõik, mida õppisime, nimelt: ip, mac, kasutajanimi.
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()
Võiksime siinkohal peatuda, kuna oleme juba loonud dünaamilise aadressiraamatu, võite küsida, kuid ma läksin kaugemale ja lisasin siia seadmete automaatse varustamise.
Selleks laaditakse eelkonfigureeritud tftp serverist alla malli konfiguratsioon, millesse teeme oma muudatused ja salvestame selle mac.cfg nime all. See tähendab, et Yealinki jaoks on kahte tüüpi konfiguratsioone, üks on globaalne ja teine kehtib konkreetse telefoni kohta ja peaks olema kujul mac_phone.cfg
Pärast kõiki failis tehtud muudatusi ja selle tftp-serverisse tagasi salvestamist anname telefonile käsu seadme varundamiseks ja taaskäivitamiseks.
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')
Peale seadme taaskäivitamist saame telefoni ekraanile oma täisnime + alati korrektselt täidetud aadressiraamatu andmebaasi kujul, siis jääb üle vaid lisada XML ja natuke PHP-d, et sisu dünaamiliselt kuvada. Selliseid näiteid on palju, isegi YEALINKil endal on need olemas.
PS: Suurema skaleeritavuse huvides saate põhiseaded (muutujad) teisaldada eraldi faili.
Allikas: www.habr.com