[lua]: core and secdownload plugin
This commit is contained in:
parent
e0370fac79
commit
ed58f7365f
|
@ -0,0 +1,109 @@
|
|||
-- contrib.lua
|
||||
|
||||
-- usage:
|
||||
-- load mod_lua and this plugin:
|
||||
-- setup {
|
||||
-- module_load "mod_lua";
|
||||
-- lua.plugin "/path/contrib.lua";
|
||||
-- }
|
||||
|
||||
local filename, args = ...
|
||||
|
||||
-- basepath for loading sub handlers with lua.handler
|
||||
-- this allows us to have lua actions that don't use the global lock
|
||||
local basepath = filename:gsub("(.*/)(.*)", "%1")
|
||||
|
||||
-- core.wsgi:
|
||||
-- WSGI applications expect the url to be split into SCRIPT_NAME and
|
||||
-- PATH_INFO; SCRIPT_NAME is their "application root", and PATH_INFO the requsted
|
||||
-- resource in the application.
|
||||
-- By default, lighttpd uses an empty PATH_INFO (unless you used the "pathinfo;" action,
|
||||
-- but this doesn't help here as we're not dealing with static files here)
|
||||
-- Important: WSGI is an "extension" of CGI; it doesn't specify a transport protocol,
|
||||
-- you can use it with plain CGI, FastCGI or SCGI (or anything else that supports
|
||||
-- the basic CGI protocol)
|
||||
--
|
||||
-- Usage:
|
||||
-- core.wsgi ( <url-prefix>, ${ <backend-actions>; } );
|
||||
--
|
||||
-- Example: Trac in "/trac", listening via FastCGI on unix:/tmp/trac.socket
|
||||
-- You oviously have to load mod_fastcgi for this.
|
||||
--
|
||||
-- core.wsgi ( "/trac", ${ fastcgi "unix:/tmp/trac.socket"; } );
|
||||
|
||||
local function wsgi(uri_prefix, act)
|
||||
if type(uri_prefix) ~= "string" then
|
||||
lighty.error("wsgi expects a string (uri-prefix) as first parameter")
|
||||
return nil
|
||||
end
|
||||
|
||||
local uri_len = uri_prefix:len()
|
||||
|
||||
local function wsgi_rewrite(vr)
|
||||
vr.phys.pathinfo = vr.req.uri.path:sub(uri_len)
|
||||
vr.req.uri.path = uri_prefix
|
||||
end
|
||||
|
||||
return action.when(request.path:prefix(uri_prefix),
|
||||
action.list(wsgi_rewrite, act)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
-- try to find a file for the current url with ".html" prefix,
|
||||
-- if url doesn't already belong to a file and has not already ".html" prefix
|
||||
-- example:
|
||||
-- core.cached_html;
|
||||
local function cached_html()
|
||||
local try_cached_html = action.lua.handler(basepath .. 'core__cached_html.lua')
|
||||
|
||||
return action.when(physical.is_file:isnot(), action.when(physical.path:notsuffix('.html'), try_cached_html))
|
||||
end
|
||||
|
||||
-- Usage
|
||||
-- auth.require_user (userlist);
|
||||
-- Require a specific authenticated user; put it after an auth action.
|
||||
-- Be careful: the empty username matches unauthenticated users.
|
||||
-- Example:
|
||||
-- auth.plain [ "method": "basic", "realm": "test", "file": "test.plain" ];
|
||||
-- auth.require_user ("foo1", "foo2");
|
||||
local function auth_require_user(...)
|
||||
local users = {...}
|
||||
-- lighty.debug("auth_require_user")
|
||||
if #users == 0 then
|
||||
lighty.error("Empty userlist in auth.require_user")
|
||||
return nil
|
||||
else
|
||||
local escapedusers = {}
|
||||
for i, u in ipairs(users) do
|
||||
escapedusers[i] = u:gsub("[]|+*[\\^$-]", "\\%0")
|
||||
end
|
||||
local regex = "^(" .. table.concat(escapedusers, "|") .. ")$"
|
||||
-- lighty.debug("auth_require_user REMOTE_USER regex: " .. regex)
|
||||
return action.when(request.environment["REMOTE_USER"]:nomatch(regex), action.auth.deny())
|
||||
end
|
||||
end
|
||||
|
||||
-- Usage:
|
||||
-- core.xsendfile docroot;
|
||||
-- The parameter is the doc-root the files has to be in (can be omitted)
|
||||
-- Example:
|
||||
-- core.xsendfile "/srv/";
|
||||
local function xsendfile(docroot)
|
||||
if docroot and type(docroot) =~ "string" then
|
||||
lighty.error("xsendfile: parameter has to be a string")
|
||||
return nil
|
||||
end
|
||||
|
||||
local handle_x_sendfile = action.lua.handler(basepath .. 'core__xsendfile.lua', nil, docroot)
|
||||
|
||||
return handle_x_sendfile
|
||||
end
|
||||
|
||||
|
||||
actions = {
|
||||
["core.wsgi"] = wsgi,
|
||||
["core.cached_html"] = cached_html,
|
||||
["auth.require_user"] = auth_require_user,
|
||||
["core.xsendfile"] = xsendfile
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
local function try_cached_html(vr)
|
||||
local p = vr.phys.path .. '.html'
|
||||
st, res = vr:stat(p)
|
||||
if st and st.is_file then
|
||||
-- found the file
|
||||
vr.phys.path = p
|
||||
elseif res == lighty.HANDLER_WAIT_FOR_EVENT then
|
||||
return lighty.HANDLER_WAIT_FOR_EVENT
|
||||
end
|
||||
-- ignore other errors
|
||||
end
|
||||
|
||||
actions = try_cached_html
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
local filename, args = ...
|
||||
|
||||
local docroot = args
|
||||
|
||||
-- create new class XFilterDrop
|
||||
local XFilterDrop = { }
|
||||
XFilterDrop.__index = XFilterDrop
|
||||
|
||||
-- "classmethod" to create new instance
|
||||
function XFilterDrop:new(vr)
|
||||
-- vr:debug("New XSendfile instance")
|
||||
local o = { }
|
||||
setmetatable(o, self)
|
||||
return o
|
||||
end
|
||||
|
||||
-- normal method to handle content
|
||||
function XFilterDrop:handle(vr, outq, inq)
|
||||
-- drop further input (we closed it already)
|
||||
inq:skip_all()
|
||||
return lighty.HANDLER_GO_ON
|
||||
end
|
||||
|
||||
-- create a new filter which drops all input and adds it to the vrequest
|
||||
-- returns the filter object so you can insert your own content in f.out (it is already closed)
|
||||
local function add_drop_filter(vr)
|
||||
local f = vr:add_filter_out(XFilterDrop:new())
|
||||
f['in'].is_closed = true
|
||||
f['in']:skip_all()
|
||||
f.out.is_closed = true
|
||||
return f
|
||||
end
|
||||
|
||||
local function handle_x_sendfile(vr)
|
||||
-- vr:debug("handle x-sendfile")
|
||||
-- wait for response
|
||||
if not vr.has_response then
|
||||
if vr.is_handled then
|
||||
-- vr:debug("x-sendfile: waiting for response headers")
|
||||
return lighty.HANDLER_WAIT_FOR_EVENT
|
||||
else
|
||||
-- vr:debug("No response handler yet, cannot handle X-Sendfile")
|
||||
return lighty.HANDLER_GO_ON
|
||||
end
|
||||
end
|
||||
-- vr:debug("handle x-sendfile: headers available")
|
||||
-- add filter if x-sendfile header is not empty
|
||||
local xs = vr.resp.headers["X-Sendfile"]
|
||||
if xs and xs ~= "" then
|
||||
xs = lighty.path_simplify(xs)
|
||||
if docroot and xs:sub(docroot.len()) =~ docroot then
|
||||
vr:error("x-sendfile: File '".. xs .. "'not in required docroot '" .. docroot .. "'")
|
||||
return lighty.HANDLER_GO_ON
|
||||
end
|
||||
|
||||
-- make sure to drop all other content from the backend
|
||||
local f = add_drop_filter(vr)
|
||||
|
||||
vr.resp.headers["X-Sendfile"] = nil -- remove header from response
|
||||
|
||||
-- Add checks for the pathname here
|
||||
|
||||
vr:debug("XSendfile:handle: pushing file '" .. xs .. "' as content")
|
||||
f.out:add({ filename = xs })
|
||||
end
|
||||
end
|
||||
|
||||
actions = handle_x_sendfile
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
-- secdownload.lua
|
||||
|
||||
-- usage:
|
||||
-- a) load mod_lua and this plugin:
|
||||
-- setup {
|
||||
-- module_load "mod_lua";
|
||||
-- lua.plugin "/path/secdownload.lua";
|
||||
-- }
|
||||
-- b) activate it anywhere you want:
|
||||
-- secdownload [ "prefix": "/sec/", "document-root": "/secret/path", "secret": "abc", "timeout": 600 ];
|
||||
|
||||
local filename, args = ...
|
||||
|
||||
-- basepath for loading sub handlers with lua.handler
|
||||
-- this allows us to have lua actions that don't use the global lock
|
||||
local basepath = filename:gsub("(.*/)(.*)", "%1")
|
||||
|
||||
local function secdownload(options)
|
||||
if type(options) ~= "table" then
|
||||
lighty.error("secdownload expects a hashtable as parameter")
|
||||
return nil
|
||||
end
|
||||
|
||||
local uri_prefix = options["prefix"]
|
||||
local doc_root = options["document-root"]
|
||||
local secret = options["secret"]
|
||||
local timeout = tonumber(options["timeout"]) or 60
|
||||
|
||||
if secret == nil then
|
||||
lighty.error("secdownload: need secret in options")
|
||||
return nil
|
||||
end
|
||||
|
||||
if doc_root == nil then
|
||||
lighty.error("secdownload: need doc_root in options")
|
||||
return nil
|
||||
end
|
||||
|
||||
if doc_root:sub(-1) ~= "/" then
|
||||
doc_root = doc_root .. "/"
|
||||
end
|
||||
|
||||
if uri_prefix == nil then
|
||||
uri_prefix = "/"
|
||||
end
|
||||
|
||||
local args = { ["prefix"] = uri_prefix, ["document-root"] = doc_root, ["secret"] = secret, ["timeout"] = timeout }
|
||||
|
||||
local handle_secdownload = action.lua.handler(basepath .. 'core__cached_html.lua', nil, args)
|
||||
|
||||
return handle_secdownload
|
||||
end
|
||||
|
||||
actions = {
|
||||
["secdownload"] = secdownload
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
local filename, args = ...
|
||||
|
||||
local uri_prefix = args["prefix"]
|
||||
local doc_root = args["document-root"]
|
||||
local secret = args["secret"]
|
||||
local timeout = tonumber(args["timeout"])
|
||||
|
||||
local function deny_access(vr, status)
|
||||
vr:handle_direct()
|
||||
vr.resp.status = status
|
||||
end
|
||||
|
||||
local function handle_secdownload(vr)
|
||||
local path = vr.req.uri.path
|
||||
local prefix_len = uri_prefix:len()
|
||||
if path:sub(1, prefix_len) == uri_prefix and not vr.is_handled then
|
||||
local md5str = path:sub(prefix_len + 1, prefix_len + 32)
|
||||
|
||||
if md5str:len() ~= 32 then return deny_access(vr, 403) end
|
||||
|
||||
if path:sub(prefix_len + 33, prefix_len + 33) ~= "/" then return deny_access(vr, 403) end
|
||||
|
||||
local slash = path:find("/", prefix_len + 34)
|
||||
if slash == nil then return deny_access(vr, 403) end
|
||||
|
||||
local ts_string = path:sub(prefix_len + 34, slash - 1)
|
||||
local timestamp = tonumber(ts_string, 16)
|
||||
if timestamp == nil then return deny_access(vr, 403) end
|
||||
|
||||
path = path:sub(slash)
|
||||
|
||||
local ts = os.time()
|
||||
if (timestamp < ts - timeout) or (timestamp > ts + timeout) then
|
||||
-- Gone, not Timeout (don't retry later)
|
||||
return deny_access(vr, 410)
|
||||
end
|
||||
|
||||
-- modify md5content as you wish :)
|
||||
local md5content = secret .. path .. ts_string
|
||||
-- vr:debug("checking md5: '" .. md5content .. "', md5: " .. md5str)
|
||||
if md5str ~= lighty.md5(secret .. path .. ts_string) then
|
||||
return deny_access(vr, 403)
|
||||
end
|
||||
|
||||
-- rewrite physical paths
|
||||
vr.phys.doc_root = doc_root
|
||||
vr.phys.path = doc_root .. path
|
||||
end
|
||||
end
|
||||
|
||||
actions = handle_secdownload
|
Loading…
Reference in New Issue