diff options
Diffstat (limited to 'libs/lucid/luasrc')
-rw-r--r-- | libs/lucid/luasrc/lucid.lua | 266 | ||||
-rw-r--r-- | libs/lucid/luasrc/lucid/tcpserver.lua | 192 |
2 files changed, 458 insertions, 0 deletions
diff --git a/libs/lucid/luasrc/lucid.lua b/libs/lucid/luasrc/lucid.lua new file mode 100644 index 000000000..62741e79f --- /dev/null +++ b/libs/lucid/luasrc/lucid.lua @@ -0,0 +1,266 @@ +--[[ +LuCI - Lua Development Framework + +Copyright 2009 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]] + +local nixio = require "nixio" +local table = require "table" +local uci = require "luci.model.uci" +local os = require "os" +local io = require "io" + +local pairs, require, pcall, assert, type = pairs, require, pcall, assert, type +local ipairs, tonumber, collectgarbage = ipairs, tonumber, collectgarbage + + +module "luci.lucid" + +local slaves = {} +local pollt = {} +local tickt = {} +local tpids = {} +local tcount = 0 +local ifaddrs = nixio.getifaddrs() + +cursor = uci.cursor() +state = uci.cursor_state() +UCINAME = "lucid" + +local cursor = cursor +local state = state +local UCINAME = UCINAME +local SSTATE = "/tmp/.lucid_store" + + + +function start() + prepare() + + local detach = cursor:get(UCINAME, "main", "daemonize") + if detach == "1" then + local stat, code, msg = daemonize() + if not stat then + nixio.syslog("crit", "Unable to detach process: " .. msg .. "\n") + ox.exit(2) + end + end + + run() +end + +function prepare() + local debug = tonumber((cursor:get(UCINAME, "main", "debug"))) + + nixio.openlog("lucid", "pid", "perror") + if debug ~= 1 then + nixio.setlogmask("warning") + end + + cursor:foreach(UCINAME, "daemon", function(config) + if config.enabled ~= "1" then + return + end + + local key = config[".name"] + if not config.slave then + nixio.syslog("crit", "Daemon "..key.." is missing a slave\n") + os.exit(1) + else + nixio.syslog("info", "Initializing daemon " .. key) + end + + state:revert(UCINAME, key) + + local daemon, code, err = prepare_daemon(config) + if daemon then + state:set(UCINAME, key, "status", "started") + nixio.syslog("info", "Prepared daemon " .. key) + else + state:set(UCINAME, key, "status", "error") + state:set(UCINAME, key, "error", err) + nixio.syslog("err", "Failed to initialize daemon "..key..": ".. + err .. "\n") + end + end) +end + +function run() + local pollint = tonumber((cursor:get(UCINAME, "main", "pollinterval"))) + + while true do + local stat, code = nixio.poll(pollt, pollint) + + if stat and stat > 0 then + for _, polle in ipairs(pollt) do + if polle.revents ~= 0 and polle.handler then + polle.handler(polle) + end + end + elseif stat == 0 then + ifaddrs = nixio.getifaddrs() + collectgarbage("collect") + end + + for _, cb in ipairs(tickt) do + cb() + end + + local pid, stat, code = nixio.wait(-1, "nohang") + while pid and pid > 0 do + tcount = tcount - 1 + if tpids[pid] and tpids[pid] ~= true then + tpids[pid](pid, stat, code) + end + pid, stat, code = nixio.wait(-1, "nohang") + end + end +end + +function register_pollfd(polle) + pollt[#pollt+1] = polle + return true +end + +function unregister_pollfd(polle) + for k, v in ipairs(pollt) do + if v == polle then + table.remove(pollt, k) + return true + end + end + return false +end + +function close_pollfds() + for k, v in ipairs(pollt) do + if v.fd and v.fd.close then + v.fd:close() + end + end +end + +function register_tick(cb) + tickt[#tickt+1] = cb + return true +end + +function unregister_tick(cb) + for k, v in ipairs(tickt) do + if v == cb then + table.remove(tickt, k) + return true + end + end + return false +end + +function create_process(threadcb, waitcb) + local threadlimit = tonumber(cursor:get(UCINAME, "main", "threadlimit")) + if threadlimit and #tpids >= tcount then + nixio.syslog("warning", "Unable to create thread: process limit reached") + return nil + end + local pid, code, err = nixio.fork() + if pid and pid ~= 0 then + tpids[pid] = waitcb + tcount = tcount + 1 + elseif pid == 0 then + local code = threadcb() + os.exit(code) + else + nixio.syslog("err", "Unable to fork(): " .. err) + end + return pid, code, err +end + +function prepare_daemon(config) + nixio.syslog("info", "Preparing daemon " .. config[".name"]) + local modname = cursor:get(UCINAME, config.slave) + if not modname then + return nil, -1, "invalid slave" + end + + local stat, module = pcall(require, _NAME .. "." .. modname) + if not stat or not module.prepare_daemon then + return nil, -2, "slave type not supported" + end + + config.slave = prepare_slave(config.slave) + + return module.prepare_daemon(config, _M) +end + +function prepare_slave(name) + local slave = slaves[name] + if not slave then + local config = cursor:get_all(UCINAME, name) + + local stat, module = pcall(require, config and config.entrypoint) + if stat then + slave = {module = module, config = config} + end + end + + if slave then + return slave + else + return nil, module + end +end + +function get_interfaces() + return ifaddrs +end + +function revoke_privileges(user, group) + if nixio.getuid() == 0 then + return nixio.setgid(group) and nixio.setuid(user) + end +end + +function securestate() + local stat = nixio.fs.stat(SSTATE) or {} + local uid = nixio.getuid() + if stat.type ~= "dir" or (stat.modedec % 100) ~= 0 or stat.uid ~= uid then + nixio.fs.remover(SSTATE) + if not nixio.fs.mkdir(SSTATE, 700) then + local errno = nixio.errno() + nixio.syslog("err", "Integrity check on secure state failed!") + return nil, errno, nixio.perror(errno) + end + end + + return uci.cursor(nil, SSTATE) +end + +function daemonize() + if nixio.getppid() == 1 then + return + end + + local pid, code, msg = nixio.fork() + if not pid then + return nil, code, msg + elseif pid > 0 then + os.exit(0) + end + + nixio.setsid() + nixio.chdir("/") + + local devnull = nixio.open("/dev/null", nixio.open_flags("rdwr")) + nixio.dup(devnull, nixio.stdin) + nixio.dup(devnull, nixio.stdout) + nixio.dup(devnull, nixio.stderr) + + return true +end
\ No newline at end of file diff --git a/libs/lucid/luasrc/lucid/tcpserver.lua b/libs/lucid/luasrc/lucid/tcpserver.lua new file mode 100644 index 000000000..22f094529 --- /dev/null +++ b/libs/lucid/luasrc/lucid/tcpserver.lua @@ -0,0 +1,192 @@ +--[[ +LuCI - Lua Development Framework + +Copyright 2009 Steven Barth <steven@midlink.org> + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]] + +local os = require "os" +local nixio = require "nixio" +local lucid = require "luci.lucid" + +local ipairs, type, require, setmetatable = ipairs, type, require, setmetatable +local pairs, print, tostring, unpack = pairs, print, tostring, unpack + +module "luci.lucid.tcpserver" + +local cursor = lucid.cursor +local UCINAME = lucid.UCINAME + +local tcpsockets = {} + + +function prepare_daemon(config, server) + nixio.syslog("info", "Preparing TCP-Daemon " .. config[".name"]) + if type(config.address) ~= "table" then + config.address = {config.address} + end + + local sockets, socket, code, err = {} + local sopts = {reuseaddr = 1} + for _, addr in ipairs(config.address) do + local host, port = addr:match("(.-):?([^:]*)") + if not host then + nixio.syslog("err", "Invalid address: " .. addr) + return nil, -5, "invalid address format" + elseif #host == 0 then + host = nil + end + socket, code, err = prepare_socket(config.family, host, port, sopts) + if socket then + sockets[#sockets+1] = socket + end + end + + nixio.syslog("info", "Sockets bound for " .. config[".name"]) + + if #sockets < 1 then + return nil, -6, "no sockets bound" + end + + nixio.syslog("info", "Preparing publishers for " .. config[".name"]) + + local publisher = {} + for k, pname in ipairs(config.publisher) do + local pdata = cursor:get_all(UCINAME, pname) + if pdata then + publisher[#publisher+1] = pdata + else + nixio.syslog("err", "Publisher " .. pname .. " not found") + end + end + + nixio.syslog("info", "Preparing TLS for " .. config[".name"]) + + local tls = prepare_tls(config.tls) + if not tls and config.encryption == "enable" then + for _, s in ipairs(sockets) do + s:close() + end + return nil, -4, "Encryption requested, but no TLS context given" + end + + nixio.syslog("info", "Invoking daemon factory for " .. config[".name"]) + local handler, err = config.slave.module.factory(publisher, config) + if not handler then + for _, s in ipairs(sockets) do + s:close() + end + return nil, -3, err + else + local pollin = nixio.poll_flags("in") + for _, s in ipairs(sockets) do + server.register_pollfd({ + fd = s, + events = pollin, + revents = 0, + handler = accept, + accept = handler, + config = config, + publisher = publisher, + tls = tls + }) + end + return true + end +end + +function accept(polle) + local socket, host, port = polle.fd:accept() + if not socket then + return nixio.syslog("warn", "accept() failed: " .. port) + end + + socket:setblocking(true) + + local function thread() + lucid.close_pollfds() + local inst = setmetatable({ + host = host, port = port, interfaces = lucid.get_interfaces() + }, {__index = polle}) + if polle.config.encryption then + socket = polle.tls:create(socket) + if not socket:accept() then + socket:close() + return nixio.syslog("warning", "TLS handshake failed: " .. host) + end + end + + return polle.accept(socket, inst) + end + + local stat = {lucid.create_process(thread)} + socket:close() + return unpack(stat) +end + +function prepare_socket(family, host, port, opts, backlog) + nixio.syslog("info", "Preparing socket for port " .. port) + backlog = backlog or 1024 + family = family or "inetany" + opts = opts or {} + + local inetany = family == "inetany" + family = inetany and "inet6" or family + + local socket, code, err = nixio.socket(family, "stream") + if not socket and inetany then + family = "inet" + socket, code, err = nixio.socket(family, "stream") + end + + if not socket then + return nil, code, err + end + + for k, v in pairs(opts) do + socket:setsockopt("socket", k, v) + end + + local stat, code, err = socket:bind(host, port) + if not stat then + return nil, code, err + end + + stat, code, err = socket:listen(backlog) + if not stat then + return nil, code, err + end + + socket:setblocking(false) + + return socket, family +end + +function prepare_tls(tlskey) + local tls = nixio.tls() + if tlskey and cursor:get(UCINAME, tlskey) then + local cert = cursor:get(UCINAME, tlskey, "cert") + if cert then + tls:set_cert(cert) + end + local key = cursor:get(UCINAME, tlskey, "key") + if key then + tls:set_key(key) + end + local ciphers = cursor:get(UCINAME, tlskey, "ciphers") + if ciphers then + if type(ciphers) == "table" then + ciphers = table.concat(ciphers, ":") + end + tls:set_ciphers(ciphers) + end + end + return tls +end
\ No newline at end of file |