dispatcher.lua 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402
  1. -- Copyright 2008 Steven Barth <steven@midlink.org>
  2. -- Copyright 2008-2015 Jo-Philipp Wich <jow@openwrt.org>
  3. -- Licensed to the public under the Apache License 2.0.
  4. local fs = require "nixio.fs"
  5. local sys = require "luci.sys"
  6. local util = require "luci.util"
  7. local http = require "luci.http"
  8. local nixio = require "nixio", require "nixio.util"
  9. module("luci.dispatcher", package.seeall)
  10. context = util.threadlocal()
  11. uci = require "luci.model.uci"
  12. i18n = require "luci.i18n"
  13. _M.fs = fs
  14. -- Index table
  15. local index = nil
  16. local function check_fs_depends(fs)
  17. local fs = require "nixio.fs"
  18. for path, kind in pairs(fs) do
  19. if kind == "directory" then
  20. local empty = true
  21. for entry in (fs.dir(path) or function() end) do
  22. empty = false
  23. break
  24. end
  25. if empty then
  26. return false
  27. end
  28. elseif kind == "executable" then
  29. if fs.stat(path, "type") ~= "reg" or not fs.access(path, "x") then
  30. return false
  31. end
  32. elseif kind == "file" then
  33. if fs.stat(path, "type") ~= "reg" then
  34. return false
  35. end
  36. end
  37. end
  38. return true
  39. end
  40. local function check_uci_depends_options(conf, s, opts)
  41. local uci = require "luci.model.uci"
  42. if type(opts) == "string" then
  43. return (s[".type"] == opts)
  44. elseif opts == true then
  45. for option, value in pairs(s) do
  46. if option:byte(1) ~= 46 then
  47. return true
  48. end
  49. end
  50. elseif type(opts) == "table" then
  51. for option, value in pairs(opts) do
  52. local sval = s[option]
  53. if type(sval) == "table" then
  54. local found = false
  55. for _, v in ipairs(sval) do
  56. if v == value then
  57. found = true
  58. break
  59. end
  60. end
  61. if not found then
  62. return false
  63. end
  64. elseif value == true then
  65. if sval == nil then
  66. return false
  67. end
  68. else
  69. if sval ~= value then
  70. return false
  71. end
  72. end
  73. end
  74. end
  75. return true
  76. end
  77. local function check_uci_depends_section(conf, sect)
  78. local uci = require "luci.model.uci"
  79. for section, options in pairs(sect) do
  80. local stype = section:match("^@([A-Za-z0-9_%-]+)$")
  81. if stype then
  82. local found = false
  83. uci:foreach(conf, stype, function(s)
  84. if check_uci_depends_options(conf, s, options) then
  85. found = true
  86. return false
  87. end
  88. end)
  89. if not found then
  90. return false
  91. end
  92. else
  93. local s = uci:get_all(conf, section)
  94. if not s or not check_uci_depends_options(conf, s, options) then
  95. return false
  96. end
  97. end
  98. end
  99. return true
  100. end
  101. local function check_uci_depends(conf)
  102. local uci = require "luci.model.uci"
  103. for config, values in pairs(conf) do
  104. if values == true then
  105. local found = false
  106. uci:foreach(config, nil, function(s)
  107. found = true
  108. return false
  109. end)
  110. if not found then
  111. return false
  112. end
  113. elseif type(values) == "table" then
  114. if not check_uci_depends_section(config, values) then
  115. return false
  116. end
  117. end
  118. end
  119. return true
  120. end
  121. local function check_depends(spec)
  122. if type(spec.depends) ~= "table" then
  123. return true
  124. end
  125. if type(spec.depends.fs) == "table" and not check_fs_depends(spec.depends.fs) then
  126. local satisfied = false
  127. local alternatives = (#spec.depends.fs > 0) and spec.depends.fs or { spec.depends.fs }
  128. for _, alternative in ipairs(alternatives) do
  129. if check_fs_depends(alternative) then
  130. satisfied = true
  131. break
  132. end
  133. end
  134. if not satisfied then
  135. return false
  136. end
  137. end
  138. if type(spec.depends.uci) == "table" then
  139. local satisfied = false
  140. local alternatives = (#spec.depends.uci > 0) and spec.depends.uci or { spec.depends.uci }
  141. for _, alternative in ipairs(alternatives) do
  142. if check_uci_depends(alternative) then
  143. satisfied = true
  144. break
  145. end
  146. end
  147. if not satisfied then
  148. return false
  149. end
  150. end
  151. return true
  152. end
  153. local function target_to_json(target, module)
  154. local action
  155. if target.type == "call" then
  156. action = {
  157. ["type"] = "call",
  158. ["module"] = module,
  159. ["function"] = target.name,
  160. ["parameters"] = target.argv
  161. }
  162. elseif target.type == "view" then
  163. action = {
  164. ["type"] = "view",
  165. ["path"] = target.view
  166. }
  167. elseif target.type == "template" then
  168. action = {
  169. ["type"] = "template",
  170. ["path"] = target.view
  171. }
  172. elseif target.type == "cbi" then
  173. action = {
  174. ["type"] = "cbi",
  175. ["path"] = target.model
  176. }
  177. elseif target.type == "form" then
  178. action = {
  179. ["type"] = "form",
  180. ["path"] = target.model
  181. }
  182. elseif target.type == "firstchild" then
  183. action = {
  184. ["type"] = "firstchild"
  185. }
  186. elseif target.type == "firstnode" then
  187. action = {
  188. ["type"] = "firstchild",
  189. ["recurse"] = true
  190. }
  191. elseif target.type == "arcombine" then
  192. if type(target.targets) == "table" then
  193. action = {
  194. ["type"] = "arcombine",
  195. ["targets"] = {
  196. target_to_json(target.targets[1], module),
  197. target_to_json(target.targets[2], module)
  198. }
  199. }
  200. end
  201. elseif target.type == "alias" then
  202. action = {
  203. ["type"] = "alias",
  204. ["path"] = table.concat(target.req, "/")
  205. }
  206. elseif target.type == "rewrite" then
  207. action = {
  208. ["type"] = "rewrite",
  209. ["path"] = table.concat(target.req, "/"),
  210. ["remove"] = target.n
  211. }
  212. end
  213. if target.post and action then
  214. action.post = target.post
  215. end
  216. return action
  217. end
  218. local function tree_to_json(node, json)
  219. local fs = require "nixio.fs"
  220. local util = require "luci.util"
  221. if type(node.nodes) == "table" then
  222. for subname, subnode in pairs(node.nodes) do
  223. local spec = {
  224. title = util.striptags(subnode.title),
  225. order = subnode.order
  226. }
  227. if subnode.leaf then
  228. spec.wildcard = true
  229. end
  230. if subnode.cors then
  231. spec.cors = true
  232. end
  233. if subnode.setuser then
  234. spec.setuser = subnode.setuser
  235. end
  236. if subnode.setgroup then
  237. spec.setgroup = subnode.setgroup
  238. end
  239. if type(subnode.target) == "table" then
  240. spec.action = target_to_json(subnode.target, subnode.module)
  241. end
  242. if type(subnode.file_depends) == "table" then
  243. for _, v in ipairs(subnode.file_depends) do
  244. spec.depends = spec.depends or {}
  245. spec.depends.fs = spec.depends.fs or {}
  246. local ft = fs.stat(v, "type")
  247. if ft == "dir" then
  248. spec.depends.fs[v] = "directory"
  249. elseif v:match("/s?bin/") then
  250. spec.depends.fs[v] = "executable"
  251. else
  252. spec.depends.fs[v] = "file"
  253. end
  254. end
  255. end
  256. if type(subnode.uci_depends) == "table" then
  257. for k, v in pairs(subnode.uci_depends) do
  258. spec.depends = spec.depends or {}
  259. spec.depends.uci = spec.depends.uci or {}
  260. spec.depends.uci[k] = v
  261. end
  262. end
  263. if (subnode.sysauth_authenticator ~= nil) or
  264. (subnode.sysauth ~= nil and subnode.sysauth ~= false)
  265. then
  266. if subnode.sysauth_authenticator == "htmlauth" then
  267. spec.auth = {
  268. login = true,
  269. methods = { "cookie:sysauth" }
  270. }
  271. elseif subname == "rpc" and subnode.module == "luci.controller.rpc" then
  272. spec.auth = {
  273. login = false,
  274. methods = { "query:auth", "cookie:sysauth" }
  275. }
  276. elseif subnode.module == "luci.controller.admin.uci" then
  277. spec.auth = {
  278. login = false,
  279. methods = { "param:sid" }
  280. }
  281. end
  282. elseif subnode.sysauth == false then
  283. spec.auth = {}
  284. end
  285. if not spec.action then
  286. spec.title = nil
  287. end
  288. spec.satisfied = check_depends(spec)
  289. json.children = json.children or {}
  290. json.children[subname] = tree_to_json(subnode, spec)
  291. end
  292. end
  293. return json
  294. end
  295. function build_url(...)
  296. local path = {...}
  297. local url = { http.getenv("SCRIPT_NAME") or "" }
  298. local p
  299. for _, p in ipairs(path) do
  300. if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then
  301. url[#url+1] = "/"
  302. url[#url+1] = p
  303. end
  304. end
  305. if #path == 0 then
  306. url[#url+1] = "/"
  307. end
  308. return table.concat(url, "")
  309. end
  310. function error404(message)
  311. http.status(404, "Not Found")
  312. message = message or "Not Found"
  313. local function render()
  314. local template = require "luci.template"
  315. template.render("error404")
  316. end
  317. if not util.copcall(render) then
  318. http.prepare_content("text/plain")
  319. http.write(message)
  320. end
  321. return false
  322. end
  323. function error500(message)
  324. util.perror(message)
  325. if not context.template_header_sent then
  326. http.status(500, "Internal Server Error")
  327. http.prepare_content("text/plain")
  328. http.write(message)
  329. else
  330. require("luci.template")
  331. if not util.copcall(luci.template.render, "error500", {message=message}) then
  332. http.prepare_content("text/plain")
  333. http.write(message)
  334. end
  335. end
  336. return false
  337. end
  338. local function determine_request_language()
  339. local conf = require "luci.config"
  340. assert(conf.main, "/etc/config/luci seems to be corrupt, unable to find section 'main'")
  341. local lang = conf.main.lang or "auto"
  342. if lang == "auto" then
  343. local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or ""
  344. for aclang in aclang:gmatch("[%w_-]+") do
  345. local country, culture = aclang:match("^([a-z][a-z])[_-]([a-zA-Z][a-zA-Z])$")
  346. if country and culture then
  347. local cc = "%s_%s" %{ country, culture:lower() }
  348. if conf.languages[cc] then
  349. lang = cc
  350. break
  351. elseif conf.languages[country] then
  352. lang = country
  353. break
  354. end
  355. elseif conf.languages[aclang] then
  356. lang = aclang
  357. break
  358. end
  359. end
  360. end
  361. if lang == "auto" then
  362. lang = i18n.default
  363. end
  364. i18n.setlanguage(lang)
  365. end
  366. function httpdispatch(request, prefix)
  367. http.context.request = request
  368. local r = {}
  369. context.request = r
  370. local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true)
  371. if prefix then
  372. for _, node in ipairs(prefix) do
  373. r[#r+1] = node
  374. end
  375. end
  376. local node
  377. for node in pathinfo:gmatch("[^/%z]+") do
  378. r[#r+1] = node
  379. end
  380. determine_request_language()
  381. local stat, err = util.coxpcall(function()
  382. dispatch(context.request)
  383. end, error500)
  384. http.close()
  385. --context._disable_memtrace()
  386. end
  387. local function require_post_security(target, args)
  388. if type(target) == "table" and target.type == "arcombine" and type(target.targets) == "table" then
  389. return require_post_security((type(args) == "table" and #args > 0) and target.targets[2] or target.targets[1], args)
  390. end
  391. if type(target) == "table" then
  392. if type(target.post) == "table" then
  393. local param_name, required_val, request_val
  394. for param_name, required_val in pairs(target.post) do
  395. request_val = http.formvalue(param_name)
  396. if (type(required_val) == "string" and
  397. request_val ~= required_val) or
  398. (required_val == true and request_val == nil)
  399. then
  400. return false
  401. end
  402. end
  403. return true
  404. end
  405. return (target.post == true)
  406. end
  407. return false
  408. end
  409. function test_post_security()
  410. if http.getenv("REQUEST_METHOD") ~= "POST" then
  411. http.status(405, "Method Not Allowed")
  412. http.header("Allow", "POST")
  413. return false
  414. end
  415. if http.formvalue("token") ~= context.authtoken then
  416. http.status(403, "Forbidden")
  417. luci.template.render("csrftoken")
  418. return false
  419. end
  420. return true
  421. end
  422. local function session_retrieve(sid, allowed_users)
  423. local sdat = util.ubus("session", "get", { ubus_rpc_session = sid })
  424. if type(sdat) == "table" and
  425. type(sdat.values) == "table" and
  426. type(sdat.values.token) == "string" and
  427. (not allowed_users or
  428. util.contains(allowed_users, sdat.values.username))
  429. then
  430. uci:set_session_id(sid)
  431. return sid, sdat.values
  432. end
  433. return nil, nil
  434. end
  435. local function session_setup(user, pass, allowed_users)
  436. if util.contains(allowed_users, user) then
  437. local login = util.ubus("session", "login", {
  438. username = user,
  439. password = pass,
  440. timeout = tonumber(luci.config.sauth.sessiontime)
  441. })
  442. local rp = context.requestpath
  443. and table.concat(context.requestpath, "/") or ""
  444. if type(login) == "table" and
  445. type(login.ubus_rpc_session) == "string"
  446. then
  447. util.ubus("session", "set", {
  448. ubus_rpc_session = login.ubus_rpc_session,
  449. values = { token = sys.uniqueid(16) }
  450. })
  451. io.stderr:write("luci: accepted login on /%s for %s from %s\n"
  452. %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
  453. return session_retrieve(login.ubus_rpc_session)
  454. end
  455. io.stderr:write("luci: failed login on /%s for %s from %s\n"
  456. %{ rp, user, http.getenv("REMOTE_ADDR") or "?" })
  457. end
  458. return nil, nil
  459. end
  460. local function check_authentication(method)
  461. local auth_type, auth_param = method:match("^(%w+):(.+)$")
  462. local sid, sdat
  463. if auth_type == "cookie" then
  464. sid = http.getcookie(auth_param)
  465. elseif auth_type == "param" then
  466. sid = http.formvalue(auth_param)
  467. elseif auth_type == "query" then
  468. sid = http.formvalue(auth_param, true)
  469. end
  470. return session_retrieve(sid)
  471. end
  472. local function get_children(node)
  473. local children = {}
  474. if not node.wildcard and type(node.children) == "table" then
  475. for name, child in pairs(node.children) do
  476. children[#children+1] = {
  477. name = name,
  478. node = child,
  479. order = child.order or 1000
  480. }
  481. end
  482. table.sort(children, function(a, b)
  483. if a.order == b.order then
  484. return a.name < b.name
  485. else
  486. return a.order < b.order
  487. end
  488. end)
  489. end
  490. return children
  491. end
  492. local function find_subnode(root, prefix, recurse, descended)
  493. local children = get_children(root)
  494. if #children > 0 and (not descended or recurse) then
  495. local sub_path = { unpack(prefix) }
  496. if recurse == false then
  497. recurse = nil
  498. end
  499. for _, child in ipairs(children) do
  500. sub_path[#prefix+1] = child.name
  501. local res_path = find_subnode(child.node, sub_path, recurse, true)
  502. if res_path then
  503. return res_path
  504. end
  505. end
  506. end
  507. if descended then
  508. if not recurse or
  509. root.action.type == "cbi" or
  510. root.action.type == "form" or
  511. root.action.type == "view" or
  512. root.action.type == "template" or
  513. root.action.type == "arcombine"
  514. then
  515. return prefix
  516. end
  517. end
  518. end
  519. local function merge_trees(node_a, node_b)
  520. for k, v in pairs(node_b) do
  521. if k == "children" then
  522. node_a.children = node_a.children or {}
  523. for name, spec in pairs(v) do
  524. node_a.children[name] = merge_trees(node_a.children[name] or {}, spec)
  525. end
  526. else
  527. node_a[k] = v
  528. end
  529. end
  530. if type(node_a.action) == "table" and
  531. node_a.action.type == "firstchild" and
  532. node_a.children == nil
  533. then
  534. node_a.satisfied = false
  535. end
  536. return node_a
  537. end
  538. function menu_json()
  539. local tree = context.tree or createtree()
  540. local lua_tree = tree_to_json(tree, {
  541. action = {
  542. ["type"] = "firstchild",
  543. ["recurse"] = true
  544. }
  545. })
  546. local json_tree = createtree_json()
  547. return merge_trees(lua_tree, json_tree)
  548. end
  549. local function init_template_engine(ctx)
  550. local tpl = require "luci.template"
  551. local media = luci.config.main.mediaurlbase
  552. if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then
  553. media = nil
  554. for name, theme in pairs(luci.config.themes) do
  555. if name:sub(1,1) ~= "." and pcall(tpl.Template,
  556. "themes/%s/header" % fs.basename(theme)) then
  557. media = theme
  558. end
  559. end
  560. assert(media, "No valid theme found")
  561. end
  562. local function _ifattr(cond, key, val, noescape)
  563. if cond then
  564. local env = getfenv(3)
  565. local scope = (type(env.self) == "table") and env.self
  566. if type(val) == "table" then
  567. if not next(val) then
  568. return ''
  569. else
  570. val = util.serialize_json(val)
  571. end
  572. end
  573. val = tostring(val or
  574. (type(env[key]) ~= "function" and env[key]) or
  575. (scope and type(scope[key]) ~= "function" and scope[key]) or "")
  576. if noescape ~= true then
  577. val = util.pcdata(val)
  578. end
  579. return string.format(' %s="%s"', tostring(key), val)
  580. else
  581. return ''
  582. end
  583. end
  584. tpl.context.viewns = setmetatable({
  585. write = http.write;
  586. include = function(name) tpl.Template(name):render(getfenv(2)) end;
  587. translate = i18n.translate;
  588. translatef = i18n.translatef;
  589. export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end;
  590. striptags = util.striptags;
  591. pcdata = util.pcdata;
  592. media = media;
  593. theme = fs.basename(media);
  594. resource = luci.config.main.resourcebase;
  595. ifattr = function(...) return _ifattr(...) end;
  596. attr = function(...) return _ifattr(true, ...) end;
  597. url = build_url;
  598. }, {__index=function(tbl, key)
  599. if key == "controller" then
  600. return build_url()
  601. elseif key == "REQUEST_URI" then
  602. return build_url(unpack(ctx.requestpath))
  603. elseif key == "FULL_REQUEST_URI" then
  604. local url = { http.getenv("SCRIPT_NAME") or "", http.getenv("PATH_INFO") }
  605. local query = http.getenv("QUERY_STRING")
  606. if query and #query > 0 then
  607. url[#url+1] = "?"
  608. url[#url+1] = query
  609. end
  610. return table.concat(url, "")
  611. elseif key == "token" then
  612. return ctx.authtoken
  613. else
  614. return rawget(tbl, key) or _G[key]
  615. end
  616. end})
  617. return tpl
  618. end
  619. function dispatch(request)
  620. --context._disable_memtrace = require "luci.debug".trap_memtrace("l")
  621. local ctx = context
  622. local auth, cors, suid, sgid
  623. local menu = menu_json()
  624. local page = menu
  625. local requested_path_full = {}
  626. local requested_path_node = {}
  627. local requested_path_args = {}
  628. for i, s in ipairs(request) do
  629. if type(page.children) ~= "table" or not page.children[s] then
  630. page = nil
  631. break
  632. end
  633. if not page.children[s].satisfied then
  634. page = nil
  635. break
  636. end
  637. page = page.children[s]
  638. auth = page.auth or auth
  639. cors = page.cors or cors
  640. suid = page.setuser or suid
  641. sgid = page.setgroup or sgid
  642. requested_path_full[i] = s
  643. requested_path_node[i] = s
  644. if page.wildcard then
  645. for j = i + 1, #request do
  646. requested_path_args[j - i] = request[j]
  647. requested_path_full[j] = request[j]
  648. end
  649. break
  650. end
  651. end
  652. local tpl = init_template_engine(ctx)
  653. ctx.args = requested_path_args
  654. ctx.path = requested_path_node
  655. ctx.dispatched = page
  656. ctx.requestpath = ctx.requestpath or requested_path_full
  657. ctx.requestargs = ctx.requestargs or requested_path_args
  658. ctx.requested = ctx.requested or page
  659. if type(auth) == "table" and type(auth.methods) == "table" and #auth.methods > 0 then
  660. local sid, sdat
  661. for _, method in ipairs(auth.methods) do
  662. sid, sdat = check_authentication(method)
  663. if sid and sdat then
  664. break
  665. end
  666. end
  667. if not (sid and sdat) and auth.login then
  668. local user = http.getenv("HTTP_AUTH_USER")
  669. local pass = http.getenv("HTTP_AUTH_PASS")
  670. if user == nil and pass == nil then
  671. user = http.formvalue("luci_username")
  672. pass = http.formvalue("luci_password")
  673. end
  674. sid, sdat = session_setup(user, pass, { "root" })
  675. if not sid then
  676. context.path = {}
  677. http.status(403, "Forbidden")
  678. http.header("X-LuCI-Login-Required", "yes")
  679. return tpl.render("sysauth", { duser = "root", fuser = user })
  680. end
  681. http.header("Set-Cookie", 'sysauth=%s; path=%s; HttpOnly%s' %{
  682. sid, build_url(), http.getenv("HTTPS") == "on" and "; secure" or ""
  683. })
  684. http.redirect(build_url(unpack(ctx.requestpath)))
  685. return
  686. end
  687. if not sid or not sdat then
  688. http.status(403, "Forbidden")
  689. http.header("X-LuCI-Login-Required", "yes")
  690. return
  691. end
  692. ctx.authsession = sid
  693. ctx.authtoken = sdat.token
  694. ctx.authuser = sdat.username
  695. end
  696. local action = (page and type(page.action) == "table") and page.action or {}
  697. if action.type == "arcombine" then
  698. action = (#requested_path_args > 0) and action.targets[2] or action.targets[1]
  699. end
  700. if cors and http.getenv("REQUEST_METHOD") == "OPTIONS" then
  701. luci.http.status(200, "OK")
  702. luci.http.header("Access-Control-Allow-Origin", http.getenv("HTTP_ORIGIN") or "*")
  703. luci.http.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
  704. return
  705. end
  706. if require_post_security(action) then
  707. if not test_post_security() then
  708. return
  709. end
  710. end
  711. if sgid then
  712. sys.process.setgroup(sgid)
  713. end
  714. if suid then
  715. sys.process.setuser(suid)
  716. end
  717. if action.type == "view" then
  718. tpl.render("view", { view = action.path })
  719. elseif action.type == "call" then
  720. local ok, mod = util.copcall(require, action.module)
  721. if not ok then
  722. error500(mod)
  723. return
  724. end
  725. local func = mod[action["function"]]
  726. assert(func ~= nil,
  727. 'Cannot resolve function "' .. action["function"] .. '". Is it misspelled or local?')
  728. assert(type(func) == "function",
  729. 'The symbol "' .. action["function"] .. '" does not refer to a function but data ' ..
  730. 'of type "' .. type(func) .. '".')
  731. local argv = (type(action.parameters) == "table" and #action.parameters > 0) and { unpack(action.parameters) } or {}
  732. for _, s in ipairs(requested_path_args) do
  733. argv[#argv + 1] = s
  734. end
  735. local ok, err = util.copcall(func, unpack(argv))
  736. if not ok then
  737. error500(err)
  738. end
  739. elseif action.type == "firstchild" then
  740. local sub_request = find_subnode(page, requested_path_full, action.recurse)
  741. if sub_request then
  742. dispatch(sub_request)
  743. else
  744. tpl.render("empty_node_placeholder", getfenv(1))
  745. end
  746. elseif action.type == "alias" then
  747. local sub_request = {}
  748. for name in action.path:gmatch("[^/]+") do
  749. sub_request[#sub_request + 1] = name
  750. end
  751. for _, s in ipairs(requested_path_args) do
  752. sub_request[#sub_request + 1] = s
  753. end
  754. dispatch(sub_request)
  755. elseif action.type == "rewrite" then
  756. local sub_request = { unpack(request) }
  757. for i = 1, action.remove do
  758. table.remove(sub_request, 1)
  759. end
  760. local n = 1
  761. for s in action.path:gmatch("[^/]+") do
  762. table.insert(sub_request, n, s)
  763. n = n + 1
  764. end
  765. for _, s in ipairs(requested_path_args) do
  766. sub_request[#sub_request + 1] = s
  767. end
  768. dispatch(sub_request)
  769. elseif action.type == "template" then
  770. tpl.render(action.path, getfenv(1))
  771. elseif action.type == "cbi" then
  772. _cbi({ config = action.config, model = action.path }, unpack(requested_path_args))
  773. elseif action.type == "form" then
  774. _form({ model = action.path }, unpack(requested_path_args))
  775. else
  776. local root = find_subnode(menu, {}, true)
  777. if not root then
  778. error404("No root node was registered, this usually happens if no module was installed.\n" ..
  779. "Install luci-mod-admin-full and retry. " ..
  780. "If the module is already installed, try removing the /tmp/luci-indexcache file.")
  781. else
  782. error404("No page is registered at '/" .. table.concat(requested_path_full, "/") .. "'.\n" ..
  783. "If this url belongs to an extension, make sure it is properly installed.\n" ..
  784. "If the extension was recently installed, try removing the /tmp/luci-indexcache file.")
  785. end
  786. end
  787. end
  788. function createindex()
  789. local controllers = { }
  790. local base = "%s/controller/" % util.libpath()
  791. local _, path
  792. for path in (fs.glob("%s*.lua" % base) or function() end) do
  793. controllers[#controllers+1] = path
  794. end
  795. for path in (fs.glob("%s*/*.lua" % base) or function() end) do
  796. controllers[#controllers+1] = path
  797. end
  798. if indexcache then
  799. local cachedate = fs.stat(indexcache, "mtime")
  800. if cachedate then
  801. local realdate = 0
  802. for _, obj in ipairs(controllers) do
  803. local omtime = fs.stat(obj, "mtime")
  804. realdate = (omtime and omtime > realdate) and omtime or realdate
  805. end
  806. if cachedate > realdate and sys.process.info("uid") == 0 then
  807. assert(
  808. sys.process.info("uid") == fs.stat(indexcache, "uid")
  809. and fs.stat(indexcache, "modestr") == "rw-------",
  810. "Fatal: Indexcache is not sane!"
  811. )
  812. index = loadfile(indexcache)()
  813. return index
  814. end
  815. end
  816. end
  817. index = {}
  818. for _, path in ipairs(controllers) do
  819. local modname = "luci.controller." .. path:sub(#base+1, #path-4):gsub("/", ".")
  820. local mod = require(modname)
  821. assert(mod ~= true,
  822. "Invalid controller file found\n" ..
  823. "The file '" .. path .. "' contains an invalid module line.\n" ..
  824. "Please verify whether the module name is set to '" .. modname ..
  825. "' - It must correspond to the file path!")
  826. local idx = mod.index
  827. if type(idx) == "function" then
  828. index[modname] = idx
  829. end
  830. end
  831. if indexcache then
  832. local f = nixio.open(indexcache, "w", 600)
  833. f:writeall(util.get_bytecode(index))
  834. f:close()
  835. end
  836. end
  837. function createtree_json()
  838. local json = require "luci.jsonc"
  839. local tree = {}
  840. local schema = {
  841. action = "table",
  842. auth = "table",
  843. cors = "boolean",
  844. depends = "table",
  845. order = "number",
  846. setgroup = "string",
  847. setuser = "string",
  848. title = "string",
  849. wildcard = "boolean"
  850. }
  851. local files = {}
  852. local fprint = {}
  853. local cachefile
  854. for file in (fs.glob("/usr/share/luci/menu.d/*.json") or function() end) do
  855. files[#files+1] = file
  856. if indexcache then
  857. local st = fs.stat(file)
  858. if st then
  859. fprint[#fprint+1] = '%x' % st.ino
  860. fprint[#fprint+1] = '%x' % st.mtime
  861. fprint[#fprint+1] = '%x' % st.size
  862. end
  863. end
  864. end
  865. if indexcache then
  866. cachefile = "%s.%s.json" %{
  867. indexcache,
  868. nixio.crypt(table.concat(fprint, "|"), "$1$"):sub(5):gsub("/", ".")
  869. }
  870. local res = json.parse(fs.readfile(cachefile) or "")
  871. if res then
  872. return res
  873. end
  874. for file in (fs.glob("%s.*.json" % indexcache) or function() end) do
  875. fs.unlink(file)
  876. end
  877. end
  878. for _, file in ipairs(files) do
  879. local data = json.parse(fs.readfile(file) or "")
  880. if type(data) == "table" then
  881. for path, spec in pairs(data) do
  882. if type(spec) == "table" then
  883. local node = tree
  884. for s in path:gmatch("[^/]+") do
  885. if s == "*" then
  886. node.wildcard = true
  887. break
  888. end
  889. node.children = node.children or {}
  890. node.children[s] = node.children[s] or {}
  891. node = node.children[s]
  892. end
  893. if node ~= tree then
  894. for k, t in pairs(schema) do
  895. if type(spec[k]) == t then
  896. node[k] = spec[k]
  897. end
  898. end
  899. node.satisfied = check_depends(spec)
  900. end
  901. end
  902. end
  903. end
  904. end
  905. if cachefile then
  906. fs.writefile(cachefile, json.stringify(tree))
  907. end
  908. return tree
  909. end
  910. -- Build the index before if it does not exist yet.
  911. function createtree()
  912. if not index then
  913. createindex()
  914. end
  915. local ctx = context
  916. local tree = {nodes={}, inreq=true}
  917. ctx.treecache = setmetatable({}, {__mode="v"})
  918. ctx.tree = tree
  919. local scope = setmetatable({}, {__index = luci.dispatcher})
  920. for k, v in pairs(index) do
  921. scope._NAME = k
  922. setfenv(v, scope)
  923. v()
  924. end
  925. return tree
  926. end
  927. function assign(path, clone, title, order)
  928. local obj = node(unpack(path))
  929. obj.nodes = nil
  930. obj.module = nil
  931. obj.title = title
  932. obj.order = order
  933. setmetatable(obj, {__index = _create_node(clone)})
  934. return obj
  935. end
  936. function entry(path, target, title, order)
  937. local c = node(unpack(path))
  938. c.target = target
  939. c.title = title
  940. c.order = order
  941. c.module = getfenv(2)._NAME
  942. return c
  943. end
  944. -- enabling the node.
  945. function get(...)
  946. return _create_node({...})
  947. end
  948. function node(...)
  949. local c = _create_node({...})
  950. c.module = getfenv(2)._NAME
  951. c.auto = nil
  952. return c
  953. end
  954. function lookup(...)
  955. local i, path = nil, {}
  956. for i = 1, select('#', ...) do
  957. local name, arg = nil, tostring(select(i, ...))
  958. for name in arg:gmatch("[^/]+") do
  959. path[#path+1] = name
  960. end
  961. end
  962. for i = #path, 1, -1 do
  963. local node = context.treecache[table.concat(path, ".", 1, i)]
  964. if node and (i == #path or node.leaf) then
  965. return node, build_url(unpack(path))
  966. end
  967. end
  968. end
  969. function _create_node(path)
  970. if #path == 0 then
  971. return context.tree
  972. end
  973. local name = table.concat(path, ".")
  974. local c = context.treecache[name]
  975. if not c then
  976. local last = table.remove(path)
  977. local parent = _create_node(path)
  978. c = {nodes={}, auto=true, inreq=true}
  979. parent.nodes[last] = c
  980. context.treecache[name] = c
  981. end
  982. return c
  983. end
  984. -- Subdispatchers --
  985. function firstchild()
  986. return { type = "firstchild" }
  987. end
  988. function firstnode()
  989. return { type = "firstnode" }
  990. end
  991. function alias(...)
  992. return { type = "alias", req = { ... } }
  993. end
  994. function rewrite(n, ...)
  995. return { type = "rewrite", n = n, req = { ... } }
  996. end
  997. function call(name, ...)
  998. return { type = "call", argv = {...}, name = name }
  999. end
  1000. function post_on(params, name, ...)
  1001. return {
  1002. type = "call",
  1003. post = params,
  1004. argv = { ... },
  1005. name = name
  1006. }
  1007. end
  1008. function post(...)
  1009. return post_on(true, ...)
  1010. end
  1011. function template(name)
  1012. return { type = "template", view = name }
  1013. end
  1014. function view(name)
  1015. return { type = "view", view = name }
  1016. end
  1017. function _cbi(self, ...)
  1018. local cbi = require "luci.cbi"
  1019. local tpl = require "luci.template"
  1020. local http = require "luci.http"
  1021. local config = self.config or {}
  1022. local maps = cbi.load(self.model, ...)
  1023. local state = nil
  1024. local i, res
  1025. for i, res in ipairs(maps) do
  1026. if util.instanceof(res, cbi.SimpleForm) then
  1027. io.stderr:write("Model %s returns SimpleForm but is dispatched via cbi(),\n"
  1028. % self.model)
  1029. io.stderr:write("please change %s to use the form() action instead.\n"
  1030. % table.concat(context.request, "/"))
  1031. end
  1032. res.flow = config
  1033. local cstate = res:parse()
  1034. if cstate and (not state or cstate < state) then
  1035. state = cstate
  1036. end
  1037. end
  1038. local function _resolve_path(path)
  1039. return type(path) == "table" and build_url(unpack(path)) or path
  1040. end
  1041. if config.on_valid_to and state and state > 0 and state < 2 then
  1042. http.redirect(_resolve_path(config.on_valid_to))
  1043. return
  1044. end
  1045. if config.on_changed_to and state and state > 1 then
  1046. http.redirect(_resolve_path(config.on_changed_to))
  1047. return
  1048. end
  1049. if config.on_success_to and state and state > 0 then
  1050. http.redirect(_resolve_path(config.on_success_to))
  1051. return
  1052. end
  1053. if config.state_handler then
  1054. if not config.state_handler(state, maps) then
  1055. return
  1056. end
  1057. end
  1058. http.header("X-CBI-State", state or 0)
  1059. if not config.noheader then
  1060. tpl.render("cbi/header", {state = state})
  1061. end
  1062. local redirect
  1063. local messages
  1064. local applymap = false
  1065. local pageaction = true
  1066. local parsechain = { }
  1067. for i, res in ipairs(maps) do
  1068. if res.apply_needed and res.parsechain then
  1069. local c
  1070. for _, c in ipairs(res.parsechain) do
  1071. parsechain[#parsechain+1] = c
  1072. end
  1073. applymap = true
  1074. end
  1075. if res.redirect then
  1076. redirect = redirect or res.redirect
  1077. end
  1078. if res.pageaction == false then
  1079. pageaction = false
  1080. end
  1081. if res.message then
  1082. messages = messages or { }
  1083. messages[#messages+1] = res.message
  1084. end
  1085. end
  1086. for i, res in ipairs(maps) do
  1087. res:render({
  1088. firstmap = (i == 1),
  1089. redirect = redirect,
  1090. messages = messages,
  1091. pageaction = pageaction,
  1092. parsechain = parsechain
  1093. })
  1094. end
  1095. if not config.nofooter then
  1096. tpl.render("cbi/footer", {
  1097. flow = config,
  1098. pageaction = pageaction,
  1099. redirect = redirect,
  1100. state = state,
  1101. autoapply = config.autoapply,
  1102. trigger_apply = applymap
  1103. })
  1104. end
  1105. end
  1106. function cbi(model, config)
  1107. return {
  1108. type = "cbi",
  1109. post = { ["cbi.submit"] = true },
  1110. config = config,
  1111. model = model
  1112. }
  1113. end
  1114. function arcombine(trg1, trg2)
  1115. return {
  1116. type = "arcombine",
  1117. env = getfenv(),
  1118. targets = {trg1, trg2}
  1119. }
  1120. end
  1121. function _form(self, ...)
  1122. local cbi = require "luci.cbi"
  1123. local tpl = require "luci.template"
  1124. local http = require "luci.http"
  1125. local maps = luci.cbi.load(self.model, ...)
  1126. local state = nil
  1127. local i, res
  1128. for i, res in ipairs(maps) do
  1129. local cstate = res:parse()
  1130. if cstate and (not state or cstate < state) then
  1131. state = cstate
  1132. end
  1133. end
  1134. http.header("X-CBI-State", state or 0)
  1135. tpl.render("header")
  1136. for i, res in ipairs(maps) do
  1137. res:render()
  1138. end
  1139. tpl.render("footer")
  1140. end
  1141. function form(model)
  1142. return {
  1143. type = "form",
  1144. post = { ["cbi.submit"] = true },
  1145. model = model
  1146. }
  1147. end
  1148. translate = i18n.translate
  1149. -- This function does not actually translate the given argument but
  1150. -- is used by build/i18n-scan.pl to find translatable entries.
  1151. function _(text)
  1152. return text
  1153. end