asterisk.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  1. -- Copyright 2009 Jo-Philipp Wich <jow@openwrt.org>
  2. -- Licensed to the public under the Apache License 2.0.
  3. module("luci.asterisk", package.seeall)
  4. require("luci.asterisk.cc_idd")
  5. local _io = require("io")
  6. local uci = require("luci.model.uci").cursor()
  7. local sys = require("luci.sys")
  8. local util = require("luci.util")
  9. AST_BIN = "/usr/sbin/asterisk"
  10. AST_FLAGS = "-r -x"
  11. --- LuCI Asterisk - Resync uci context
  12. function uci_resync()
  13. uci = luci.model.uci.cursor()
  14. end
  15. --- LuCI Asterisk io interface
  16. -- Handles low level io.
  17. -- @type module
  18. io = luci.util.class()
  19. --- Execute command and return output
  20. -- @param command String containing the command to execute
  21. -- @return String containing the command output
  22. function io.exec(command)
  23. local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
  24. assert(fh, "Failed to invoke asterisk")
  25. local buffer = fh:read("*a")
  26. fh:close()
  27. return buffer
  28. end
  29. --- Execute command and invoke given callback for each readed line
  30. -- @param command String containing the command to execute
  31. -- @param callback Function to call back for each line
  32. -- @return Always true
  33. function io.execl(command, callback)
  34. local ln
  35. local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
  36. assert(fh, "Failed to invoke asterisk")
  37. repeat
  38. ln = fh:read("*l")
  39. callback(ln)
  40. until not ln
  41. fh:close()
  42. return true
  43. end
  44. --- Execute command and return an iterator that returns one line per invokation
  45. -- @param command String containing the command to execute
  46. -- @return Iterator function
  47. function io.execi(command)
  48. local fh = _io.popen( "%s %s %q" %{ AST_BIN, AST_FLAGS, command }, "r" )
  49. assert(fh, "Failed to invoke asterisk")
  50. return function()
  51. local ln = fh:read("*l")
  52. if not ln then fh:close() end
  53. return ln
  54. end
  55. end
  56. --- LuCI Asterisk - core status
  57. core = luci.util.class()
  58. --- Retrive version string.
  59. -- @return String containing the reported asterisk version
  60. function core.version(self)
  61. local version = io.exec("core show version")
  62. return version:gsub(" *\n", "")
  63. end
  64. --- LuCI Asterisk - SIP information.
  65. -- @type module
  66. sip = luci.util.class()
  67. --- Get a list of known SIP peers
  68. -- @return Table containing each SIP peer
  69. function sip.peers(self)
  70. local head = false
  71. local peers = { }
  72. for line in io.execi("sip show peers") do
  73. if not head then
  74. head = true
  75. elseif not line:match(" sip peers ") then
  76. local online, delay, id, uid
  77. local name, host, dyn, nat, acl, port, status =
  78. line:match("(.-) +(.-) +([D ]) ([N ]) (.) (%d+) +(.+)")
  79. if host == '(Unspecified)' then host = nil end
  80. if port == '0' then port = nil else port = tonumber(port) end
  81. dyn = ( dyn == 'D' and true or false )
  82. nat = ( nat == 'N' and true or false )
  83. acl = ( acl ~= ' ' and true or false )
  84. online, delay = status:match("(OK) %((%d+) ms%)")
  85. if online == 'OK' then
  86. online = true
  87. delay = tonumber(delay)
  88. elseif status ~= 'Unmonitored' then
  89. online = false
  90. delay = 0
  91. else
  92. online = nil
  93. delay = 0
  94. end
  95. id, uid = name:match("(.+)/(.+)")
  96. if not ( id and uid ) then
  97. id = name .. "..."
  98. uid = nil
  99. end
  100. peers[#peers+1] = {
  101. online = online,
  102. delay = delay,
  103. name = id,
  104. user = uid,
  105. dynamic = dyn,
  106. nat = nat,
  107. acl = acl,
  108. host = host,
  109. port = port
  110. }
  111. end
  112. end
  113. return peers
  114. end
  115. --- Get informations of given SIP peer
  116. -- @param peer String containing the name of the SIP peer
  117. function sip.peer(peer)
  118. local info = { }
  119. local keys = { }
  120. for line in io.execi("sip show peer " .. peer) do
  121. if #line > 0 then
  122. local key, val = line:match("(.-) *: +(.*)")
  123. if key and val then
  124. key = key:gsub("^ +",""):gsub(" +$", "")
  125. val = val:gsub("^ +",""):gsub(" +$", "")
  126. if key == "* Name" then
  127. key = "Name"
  128. elseif key == "Addr->IP" then
  129. info.address, info.port = val:match("(.+) Port (.+)")
  130. info.port = tonumber(info.port)
  131. elseif key == "Status" then
  132. info.online, info.delay = val:match("(OK) %((%d+) ms%)")
  133. if info.online == 'OK' then
  134. info.online = true
  135. info.delay = tonumber(info.delay)
  136. elseif status ~= 'Unmonitored' then
  137. info.online = false
  138. info.delay = 0
  139. else
  140. info.online = nil
  141. info.delay = 0
  142. end
  143. end
  144. if val == 'Yes' or val == 'yes' or val == '<Set>' then
  145. val = true
  146. elseif val == 'No' or val == 'no' then
  147. val = false
  148. elseif val == '<Not set>' or val == '(none)' then
  149. val = nil
  150. end
  151. keys[#keys+1] = key
  152. info[key] = val
  153. end
  154. end
  155. end
  156. return info, keys
  157. end
  158. --- LuCI Asterisk - Internal helpers
  159. -- @type module
  160. tools = luci.util.class()
  161. --- Convert given value to a list of tokens. Split by white space.
  162. -- @param val String or table value
  163. -- @return Table containing tokens
  164. function tools.parse_list(v)
  165. local tokens = { }
  166. v = type(v) == "table" and v or { v }
  167. for _, v in ipairs(v) do
  168. if type(v) == "string" then
  169. for v in v:gmatch("(%S+)") do
  170. tokens[#tokens+1] = v
  171. end
  172. end
  173. end
  174. return tokens
  175. end
  176. --- Convert given list to a collection of hyperlinks
  177. -- @param list Table of tokens
  178. -- @param url String pattern or callback function to construct urls (optional)
  179. -- @param sep String containing the seperator (optional, default is ", ")
  180. -- @return String containing the html fragment
  181. function tools.hyperlinks(list, url, sep)
  182. local html
  183. local function mkurl(p, t)
  184. if type(p) == "string" then
  185. return p:format(t)
  186. elseif type(p) == "function" then
  187. return p(t)
  188. else
  189. return '#'
  190. end
  191. end
  192. list = list or { }
  193. url = url or "%s"
  194. sep = sep or ", "
  195. for _, token in ipairs(list) do
  196. html = ( html and html .. sep or '' ) ..
  197. '<a href="%s">%s</a>' %{ mkurl(url, token), token }
  198. end
  199. return html or ''
  200. end
  201. --- LuCI Asterisk - International Direct Dialing Prefixes
  202. -- @type module
  203. idd = luci.util.class()
  204. --- Lookup the country name for the given IDD code.
  205. -- @param country String containing IDD code
  206. -- @return String containing the country name
  207. function idd.country(c)
  208. for _, v in ipairs(cc_idd.CC_IDD) do
  209. if type(v[3]) == "table" then
  210. for _, v2 in ipairs(v[3]) do
  211. if v2 == tostring(c) then
  212. return v[1]
  213. end
  214. end
  215. elseif v[3] == tostring(c) then
  216. return v[1]
  217. end
  218. end
  219. end
  220. --- Lookup the country code for the given IDD code.
  221. -- @param country String containing IDD code
  222. -- @return Table containing the country code(s)
  223. function idd.cc(c)
  224. for _, v in ipairs(cc_idd.CC_IDD) do
  225. if type(v[3]) == "table" then
  226. for _, v2 in ipairs(v[3]) do
  227. if v2 == tostring(c) then
  228. return type(v[2]) == "table"
  229. and v[2] or { v[2] }
  230. end
  231. end
  232. elseif v[3] == tostring(c) then
  233. return type(v[2]) == "table"
  234. and v[2] or { v[2] }
  235. end
  236. end
  237. end
  238. --- Lookup the IDD code(s) for the given country.
  239. -- @param idd String containing the country name
  240. -- @return Table containing the IDD code(s)
  241. function idd.idd(c)
  242. for _, v in ipairs(cc_idd.CC_IDD) do
  243. if v[1]:lower():match(c:lower()) then
  244. return type(v[3]) == "table"
  245. and v[3] or { v[3] }
  246. end
  247. end
  248. end
  249. --- Populate given CBI field with IDD codes.
  250. -- @param field CBI option object
  251. -- @return (nothing)
  252. function idd.cbifill(o)
  253. for i, v in ipairs(cc_idd.CC_IDD) do
  254. o:value("_%i" % i, util.pcdata(v[1]))
  255. end
  256. o.formvalue = function(...)
  257. local val = luci.cbi.Value.formvalue(...)
  258. if val:sub(1,1) == "_" then
  259. val = tonumber((val:gsub("^_", "")))
  260. if val then
  261. return type(cc_idd.CC_IDD[val][3]) == "table"
  262. and cc_idd.CC_IDD[val][3] or { cc_idd.CC_IDD[val][3] }
  263. end
  264. end
  265. return val
  266. end
  267. o.cfgvalue = function(...)
  268. local val = luci.cbi.Value.cfgvalue(...)
  269. if val then
  270. val = tools.parse_list(val)
  271. for i, v in ipairs(cc_idd.CC_IDD) do
  272. if type(v[3]) == "table" then
  273. if v[3][1] == val[1] then
  274. return "_%i" % i
  275. end
  276. else
  277. if v[3] == val[1] then
  278. return "_%i" % i
  279. end
  280. end
  281. end
  282. end
  283. return val
  284. end
  285. end
  286. --- LuCI Asterisk - Country Code Prefixes
  287. -- @type module
  288. cc = luci.util.class()
  289. --- Lookup the country name for the given CC code.
  290. -- @param country String containing CC code
  291. -- @return String containing the country name
  292. function cc.country(c)
  293. for _, v in ipairs(cc_idd.CC_IDD) do
  294. if type(v[2]) == "table" then
  295. for _, v2 in ipairs(v[2]) do
  296. if v2 == tostring(c) then
  297. return v[1]
  298. end
  299. end
  300. elseif v[2] == tostring(c) then
  301. return v[1]
  302. end
  303. end
  304. end
  305. --- Lookup the international dialing code for the given CC code.
  306. -- @param cc String containing CC code
  307. -- @return String containing IDD code
  308. function cc.idd(c)
  309. for _, v in ipairs(cc_idd.CC_IDD) do
  310. if type(v[2]) == "table" then
  311. for _, v2 in ipairs(v[2]) do
  312. if v2 == tostring(c) then
  313. return type(v[3]) == "table"
  314. and v[3] or { v[3] }
  315. end
  316. end
  317. elseif v[2] == tostring(c) then
  318. return type(v[3]) == "table"
  319. and v[3] or { v[3] }
  320. end
  321. end
  322. end
  323. --- Lookup the CC code(s) for the given country.
  324. -- @param country String containing the country name
  325. -- @return Table containing the CC code(s)
  326. function cc.cc(c)
  327. for _, v in ipairs(cc_idd.CC_IDD) do
  328. if v[1]:lower():match(c:lower()) then
  329. return type(v[2]) == "table"
  330. and v[2] or { v[2] }
  331. end
  332. end
  333. end
  334. --- Populate given CBI field with CC codes.
  335. -- @param field CBI option object
  336. -- @return (nothing)
  337. function cc.cbifill(o)
  338. for i, v in ipairs(cc_idd.CC_IDD) do
  339. o:value("_%i" % i, util.pcdata(v[1]))
  340. end
  341. o.formvalue = function(...)
  342. local val = luci.cbi.Value.formvalue(...)
  343. if val:sub(1,1) == "_" then
  344. val = tonumber((val:gsub("^_", "")))
  345. if val then
  346. return type(cc_idd.CC_IDD[val][2]) == "table"
  347. and cc_idd.CC_IDD[val][2] or { cc_idd.CC_IDD[val][2] }
  348. end
  349. end
  350. return val
  351. end
  352. o.cfgvalue = function(...)
  353. local val = luci.cbi.Value.cfgvalue(...)
  354. if val then
  355. val = tools.parse_list(val)
  356. for i, v in ipairs(cc_idd.CC_IDD) do
  357. if type(v[2]) == "table" then
  358. if v[2][1] == val[1] then
  359. return "_%i" % i
  360. end
  361. else
  362. if v[2] == val[1] then
  363. return "_%i" % i
  364. end
  365. end
  366. end
  367. end
  368. return val
  369. end
  370. end
  371. --- LuCI Asterisk - Dialzone
  372. -- @type module
  373. dialzone = luci.util.class()
  374. --- Parse a dialzone section
  375. -- @param zone Table containing the zone info
  376. -- @return Table with parsed information
  377. function dialzone.parse(z)
  378. if z['.name'] then
  379. return {
  380. trunks = tools.parse_list(z.uses),
  381. name = z['.name'],
  382. description = z.description or z['.name'],
  383. addprefix = z.addprefix,
  384. matches = tools.parse_list(z.match),
  385. intlmatches = tools.parse_list(z.international),
  386. countrycode = z.countrycode,
  387. localzone = z.localzone,
  388. localprefix = z.localprefix
  389. }
  390. end
  391. end
  392. --- Get a list of known dial zones
  393. -- @return Associative table of zones and table of zone names
  394. function dialzone.zones()
  395. local zones = { }
  396. local znames = { }
  397. uci:foreach("asterisk", "dialzone",
  398. function(z)
  399. zones[z['.name']] = dialzone.parse(z)
  400. znames[#znames+1] = z['.name']
  401. end)
  402. return zones, znames
  403. end
  404. --- Get a specific dial zone
  405. -- @param name Name of the dial zone
  406. -- @return Table containing zone information
  407. function dialzone.zone(n)
  408. local zone
  409. uci:foreach("asterisk", "dialzone",
  410. function(z)
  411. if z['.name'] == n then
  412. zone = dialzone.parse(z)
  413. end
  414. end)
  415. return zone
  416. end
  417. --- Find uci section hash for given zone number
  418. -- @param idx Zone number
  419. -- @return String containing the uci hash pointing to the section
  420. function dialzone.ucisection(i)
  421. local hash
  422. local index = 1
  423. i = tonumber(i)
  424. uci:foreach("asterisk", "dialzone",
  425. function(z)
  426. if not hash and index == i then
  427. hash = z['.name']
  428. end
  429. index = index + 1
  430. end)
  431. return hash
  432. end
  433. --- LuCI Asterisk - Voicemailbox
  434. -- @type module
  435. voicemail = luci.util.class()
  436. --- Parse a voicemail section
  437. -- @param zone Table containing the mailbox info
  438. -- @return Table with parsed information
  439. function voicemail.parse(z)
  440. if z.number and #z.number > 0 then
  441. local v = {
  442. id = '%s@%s' %{ z.number, z.context or 'default' },
  443. number = z.number,
  444. context = z.context or 'default',
  445. name = z.name or z['.name'] or 'OpenWrt',
  446. zone = z.zone or 'homeloc',
  447. password = z.password or '0000',
  448. email = z.email or '',
  449. page = z.page or '',
  450. dialplans = { }
  451. }
  452. uci:foreach("asterisk", "dialplanvoice",
  453. function(s)
  454. if s.dialplan and #s.dialplan > 0 and
  455. s.voicebox == v.number
  456. then
  457. v.dialplans[#v.dialplans+1] = s.dialplan
  458. end
  459. end)
  460. return v
  461. end
  462. end
  463. --- Get a list of known voicemail boxes
  464. -- @return Associative table of boxes and table of box numbers
  465. function voicemail.boxes()
  466. local vboxes = { }
  467. local vnames = { }
  468. uci:foreach("asterisk", "voicemail",
  469. function(z)
  470. local v = voicemail.parse(z)
  471. if v then
  472. local n = '%s@%s' %{ v.number, v.context }
  473. vboxes[n] = v
  474. vnames[#vnames+1] = n
  475. end
  476. end)
  477. return vboxes, vnames
  478. end
  479. --- Get a specific voicemailbox
  480. -- @param number Number of the voicemailbox
  481. -- @return Table containing mailbox information
  482. function voicemail.box(n)
  483. local box
  484. n = n:gsub("@.+$","")
  485. uci:foreach("asterisk", "voicemail",
  486. function(z)
  487. if z.number == tostring(n) then
  488. box = voicemail.parse(z)
  489. end
  490. end)
  491. return box
  492. end
  493. --- Find all voicemailboxes within the given dialplan
  494. -- @param plan Dialplan name or table
  495. -- @return Associative table containing extensions mapped to mailbox info
  496. function voicemail.in_dialplan(p)
  497. local plan = type(p) == "string" and p or p.name
  498. local boxes = { }
  499. uci:foreach("asterisk", "dialplanvoice",
  500. function(s)
  501. if s.extension and #s.extension > 0 and s.dialplan == plan then
  502. local box = voicemail.box(s.voicebox)
  503. if box then
  504. boxes[s.extension] = box
  505. end
  506. end
  507. end)
  508. return boxes
  509. end
  510. --- Remove voicemailbox and associated extensions from config
  511. -- @param box Voicemailbox number or table
  512. -- @param ctx UCI context to use (optional)
  513. -- @return Boolean indicating success
  514. function voicemail.remove(v, ctx)
  515. ctx = ctx or uci
  516. local box = type(v) == "string" and v or v.number
  517. local ok1 = ctx:delete_all("asterisk", "voicemail", {number=box})
  518. local ok2 = ctx:delete_all("asterisk", "dialplanvoice", {voicebox=box})
  519. return ( ok1 or ok2 ) and true or false
  520. end
  521. --- LuCI Asterisk - MeetMe Conferences
  522. -- @type module
  523. meetme = luci.util.class()
  524. --- Parse a meetme section
  525. -- @param room Table containing the room info
  526. -- @return Table with parsed information
  527. function meetme.parse(r)
  528. if r.room and #r.room > 0 then
  529. local v = {
  530. room = r.room,
  531. pin = r.pin or '',
  532. adminpin = r.adminpin or '',
  533. description = r._description or '',
  534. dialplans = { }
  535. }
  536. uci:foreach("asterisk", "dialplanmeetme",
  537. function(s)
  538. if s.dialplan and #s.dialplan > 0 and s.room == v.room then
  539. v.dialplans[#v.dialplans+1] = s.dialplan
  540. end
  541. end)
  542. return v
  543. end
  544. end
  545. --- Get a list of known meetme rooms
  546. -- @return Associative table of rooms and table of room numbers
  547. function meetme.rooms()
  548. local mrooms = { }
  549. local mnames = { }
  550. uci:foreach("asterisk", "meetme",
  551. function(r)
  552. local v = meetme.parse(r)
  553. if v then
  554. mrooms[v.room] = v
  555. mnames[#mnames+1] = v.room
  556. end
  557. end)
  558. return mrooms, mnames
  559. end
  560. --- Get a specific meetme room
  561. -- @param number Number of the room
  562. -- @return Table containing room information
  563. function meetme.room(n)
  564. local room
  565. uci:foreach("asterisk", "meetme",
  566. function(r)
  567. if r.room == tostring(n) then
  568. room = meetme.parse(r)
  569. end
  570. end)
  571. return room
  572. end
  573. --- Find all meetme rooms within the given dialplan
  574. -- @param plan Dialplan name or table
  575. -- @return Associative table containing extensions mapped to room info
  576. function meetme.in_dialplan(p)
  577. local plan = type(p) == "string" and p or p.name
  578. local rooms = { }
  579. uci:foreach("asterisk", "dialplanmeetme",
  580. function(s)
  581. if s.extension and #s.extension > 0 and s.dialplan == plan then
  582. local room = meetme.room(s.room)
  583. if room then
  584. rooms[s.extension] = room
  585. end
  586. end
  587. end)
  588. return rooms
  589. end
  590. --- Remove meetme room and associated extensions from config
  591. -- @param room Voicemailbox number or table
  592. -- @param ctx UCI context to use (optional)
  593. -- @return Boolean indicating success
  594. function meetme.remove(v, ctx)
  595. ctx = ctx or uci
  596. local room = type(v) == "string" and v or v.number
  597. local ok1 = ctx:delete_all("asterisk", "meetme", {room=room})
  598. local ok2 = ctx:delete_all("asterisk", "dialplanmeetme", {room=room})
  599. return ( ok1 or ok2 ) and true or false
  600. end
  601. --- LuCI Asterisk - Dialplan
  602. -- @type module
  603. dialplan = luci.util.class()
  604. --- Parse a dialplan section
  605. -- @param plan Table containing the plan info
  606. -- @return Table with parsed information
  607. function dialplan.parse(z)
  608. if z['.name'] then
  609. local plan = {
  610. zones = { },
  611. name = z['.name'],
  612. description = z.description or z['.name']
  613. }
  614. -- dialzones
  615. for _, name in ipairs(tools.parse_list(z.include)) do
  616. local zone = dialzone.zone(name)
  617. if zone then
  618. plan.zones[#plan.zones+1] = zone
  619. end
  620. end
  621. -- voicemailboxes
  622. plan.voicemailboxes = voicemail.in_dialplan(plan)
  623. -- meetme conferences
  624. plan.meetmerooms = meetme.in_dialplan(plan)
  625. return plan
  626. end
  627. end
  628. --- Get a list of known dial plans
  629. -- @return Associative table of plans and table of plan names
  630. function dialplan.plans()
  631. local plans = { }
  632. local pnames = { }
  633. uci:foreach("asterisk", "dialplan",
  634. function(p)
  635. plans[p['.name']] = dialplan.parse(p)
  636. pnames[#pnames+1] = p['.name']
  637. end)
  638. return plans, pnames
  639. end
  640. --- Get a specific dial plan
  641. -- @param name Name of the dial plan
  642. -- @return Table containing plan information
  643. function dialplan.plan(n)
  644. local plan
  645. uci:foreach("asterisk", "dialplan",
  646. function(p)
  647. if p['.name'] == n then
  648. plan = dialplan.parse(p)
  649. end
  650. end)
  651. return plan
  652. end