GSoC Commit #1: LuCId + HTTP-Server
authorSteven Barth <steven@midlink.org>
Sat, 23 May 2009 17:21:36 +0000 (17:21 +0000)
committerSteven Barth <steven@midlink.org>
Sat, 23 May 2009 17:21:36 +0000 (17:21 +0000)
20 files changed:
Makefile
build/module.mk
build/setup.lua [new file with mode: 0644]
libs/lucid-http/Makefile [new file with mode: 0644]
libs/lucid-http/luasrc/lucid/http.lua [new file with mode: 0644]
libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua [new file with mode: 0644]
libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua [new file with mode: 0644]
libs/lucid-http/luasrc/lucid/http/Redirector.lua [new file with mode: 0644]
libs/lucid-http/luasrc/lucid/http/handler/catchall.lua [new file with mode: 0644]
libs/lucid-http/luasrc/lucid/http/handler/file.lua [new file with mode: 0644]
libs/lucid-http/luasrc/lucid/http/handler/luci.lua [new file with mode: 0644]
libs/lucid-http/luasrc/lucid/http/server.lua [new file with mode: 0644]
libs/lucid/Makefile [new file with mode: 0644]
libs/lucid/hostfiles/etc/config/lucid [new file with mode: 0644]
libs/lucid/luasrc/lucid.lua [new file with mode: 0644]
libs/lucid/luasrc/lucid/tcpserver.lua [new file with mode: 0644]
libs/lucid/root/etc/config/lucid [new file with mode: 0644]
libs/nixio/.gitignore
libs/nixio/lua/nixio/fs.lua
libs/nixio/lua/nixio/util.lua

index 1b9e259..0f0bf42 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -52,10 +52,11 @@ runboa: hostenv
 runhttpd: hostenv
        build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) "$(realpath host/usr/bin/lucittpd) $(realpath host)/usr/lib/lucittpd/plugins"
 
-runluci: runhttpd
+runluci: luahost
+       build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) "$(realpath libs/httpd/host/runluci) $(realpath host) $(HTDOCS)"
 
 runlua: hostenv
-       build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) lua
+       build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) "lua -i build/setup.lua"
 
 runshell: hostenv
        build/hostenv.sh $(realpath host) $(LUA_MODULEDIR) $(LUA_LIBRARYDIR) $$SHELL
index ecc7a92..569de10 100644 (file)
@@ -38,9 +38,6 @@ luastrip: luasource
 luacompile: luasource
        for i in $$(find dist -name *.lua -not -name debug.lua); do $(LUAC) $(LUAC_OPTIONS) -o $$i $$i; done
 
-luagzip: luacompile
-       for i in $$(find dist -name *.lua -not -name debug.lua); do gzip -f9 $$i; done
-
 luaclean:
        rm -rf dist
 
diff --git a/build/setup.lua b/build/setup.lua
new file mode 100644 (file)
index 0000000..dbe9bdc
--- /dev/null
@@ -0,0 +1,11 @@
+            local SYSROOT = os.getenv("LUCI_SYSROOT")
+            require "uci"
+            require "luci.model.uci".cursor = function(config, save)
+                    return uci.cursor(config or SYSROOT .. "/etc/config", save or SYSROOT .. "/tmp/.uci")
+            end
+
+            local x = require "luci.uvl".UVL.__init__
+            require "luci.uvl".UVL.__init__ = function(self, schemedir)
+                    x(self, schemedir or SYSROOT .. "/lib/uci/schema")
+            end
+
diff --git a/libs/lucid-http/Makefile b/libs/lucid-http/Makefile
new file mode 100644 (file)
index 0000000..2bdfad1
--- /dev/null
@@ -0,0 +1,2 @@
+include ../../build/module.mk
+include ../../build/config.mk
\ No newline at end of file
diff --git a/libs/lucid-http/luasrc/lucid/http.lua b/libs/lucid-http/luasrc/lucid/http.lua
new file mode 100644 (file)
index 0000000..32ba579
--- /dev/null
@@ -0,0 +1,33 @@
+--[[
+LuCI - Lua Configuration Interface
+
+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 require, ipairs, pcall = require, ipairs, pcall
+local srv = require "luci.lucid.http.server"
+
+module "luci.lucid.http"
+
+function factory(publisher)
+       local server = srv.Server()
+       for _, r in ipairs(publisher) do
+               local t = r[".type"]
+               local s, mod = pcall(require, "luci.lucid.http." .. (r[".type"] or ""))
+               if s and mod then
+                       mod.factory(server, r)
+               else
+                       return nil, mod
+               end
+       end
+
+       return function(...) return server:process(...) end
+end
\ No newline at end of file
diff --git a/libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua b/libs/lucid-http/luasrc/lucid/http/DirectoryPublisher.lua
new file mode 100644 (file)
index 0000000..f471781
--- /dev/null
@@ -0,0 +1,47 @@
+--[[
+LuCId HTTP-Slave
+(c) 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 ipairs, require, tostring, type = ipairs, require, tostring, type
+local file = require "luci.lucid.http.handler.file"
+local srv = require "luci.lucid.http.server"
+
+module "luci.lucid.http.DirectoryPublisher"
+
+
+function factory(server, config)
+       config.domain = config.domain or ""
+       local vhost = server:get_vhosts()[config.domain] 
+       if not vhost then
+               vhost = srv.VHost()
+               server:set_vhost(config.domain, vhost)
+       end
+
+       local handler = file.Simple(config.name, config.physical, config)
+       if config.read then
+               for _, r in ipairs(config.read) do
+                       if r:sub(1,1) == ":" then
+                               handler:restrict({interface = r:sub(2)})
+                       else
+                               handler:restrict({user = r})
+                       end
+               end
+       end
+       
+       if type(config.virtual) == "table" then
+               for _, v in ipairs(config.virtual) do
+                       vhost:set_handler(v, handler)
+               end
+       else
+               vhost:set_handler(config.virtual, handler)
+       end
+end
\ No newline at end of file
diff --git a/libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua b/libs/lucid-http/luasrc/lucid/http/LuciWebPublisher.lua
new file mode 100644 (file)
index 0000000..0d06489
--- /dev/null
@@ -0,0 +1,62 @@
+--[[
+LuCId HTTP-Slave
+(c) 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 ipairs, pcall, type = ipairs, pcall, type
+local luci = require "luci.lucid.http.handler.luci"
+local srv = require "luci.lucid.http.server"
+
+
+module "luci.lucid.http.LuciWebPublisher"
+
+function factory(server, config)
+       pcall(function()
+               require "luci.dispatcher"
+               require "luci.cbi"
+       end)
+
+       config.domain = config.domain or ""
+       local vhost = server:get_vhosts()[config.domain] 
+       if not vhost then
+               vhost = srv.VHost()
+               server:set_vhost(config.domain, vhost)
+       end
+
+       local prefix
+       if config.physical and #config.physical > 0 then
+               prefix = {}
+               for k in config.physical:gmatch("[^/]+") do
+                       if #k > 0 then
+                               prefix[#prefix+1] = k
+                       end
+               end
+       end
+
+       local handler = luci.Luci(config.name, prefix)
+       if config.exec then
+               for _, r in ipairs(config.exec) do
+                       if r:sub(1,1) == ":" then
+                               handler:restrict({interface = r:sub(2)})
+                       else
+                               handler:restrict({user = r})
+                       end
+               end
+       end
+
+       if type(config.virtual) == "table" then
+               for _, v in ipairs(config.virtual) do
+                       vhost:set_handler(v, handler)
+               end
+       else
+               vhost:set_handler(config.virtual, handler)
+       end
+end
\ No newline at end of file
diff --git a/libs/lucid-http/luasrc/lucid/http/Redirector.lua b/libs/lucid-http/luasrc/lucid/http/Redirector.lua
new file mode 100644 (file)
index 0000000..c0af90b
--- /dev/null
@@ -0,0 +1,31 @@
+--[[
+LuCId HTTP-Slave
+(c) 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 ipairs = ipairs
+local catchall = require "luci.lucid.http.handler.catchall"
+local srv = require "luci.lucid.http.server"
+
+module "luci.lucid.http.Redirector"
+
+
+function factory(server, config)
+       config.domain = config.domain or ""
+       local vhost = server:get_vhosts()[config.domain] 
+       if not vhost then
+               vhost = srv.VHost()
+               server:set_vhost(config.domain, vhost)
+       end
+
+       local handler = catchall.Redirect(config.name, config.physical)
+       vhost:set_handler(config.virtual, handler)
+end
\ No newline at end of file
diff --git a/libs/lucid-http/luasrc/lucid/http/handler/catchall.lua b/libs/lucid-http/luasrc/lucid/http/handler/catchall.lua
new file mode 100644 (file)
index 0000000..0523751
--- /dev/null
@@ -0,0 +1,53 @@
+--[[
+LuCId HTTP-Slave
+(c) 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 srv = require "luci.lucid.http.server"
+local proto = require "luci.http.protocol"
+
+module "luci.lucid.http.handler.catchall"
+
+Redirect = util.class(srv.Handler)
+
+function Redirect.__init__(self, name, target)
+       srv.Handler.__init__(self, name)
+       self.target = target
+end
+
+function Redirect.handle_GET(self, request)
+       local target = self.target
+       local protocol = request.env.HTTPS and "https://" or "http://"
+       local server = request.env.SERVER_ADDR
+       if server:find(":") then
+               server = "[" .. server .. "]"
+       end
+
+       if self.target:sub(1,1) == ":" then
+               target = protocol .. server .. target
+       end
+
+       local s, e = target:find("%TARGET%", 1, true)
+       if s then
+               local req = protocol .. (request.env.HTTP_HOST or server)
+                       .. request.env.REQUEST_URI 
+               target = target:sub(1, s-1) .. req .. target:sub(e+1)
+       end
+
+       return 302, { Location = target }
+end
+
+Redirect.handle_POST = Redirect.handle_GET
+
+function Redirect.handle_HEAD(self, request)
+       local stat, head = self:handle_GET(request)
+       return stat, head
+end
\ No newline at end of file
diff --git a/libs/lucid-http/luasrc/lucid/http/handler/file.lua b/libs/lucid-http/luasrc/lucid/http/handler/file.lua
new file mode 100644 (file)
index 0000000..d08e470
--- /dev/null
@@ -0,0 +1,250 @@
+--[[
+
+HTTP server implementation for LuCI - file handler
+(c) 2008 Steven Barth <steven@midlink.org>
+(c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
+
+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 ipairs, type, tonumber = ipairs, type, tonumber
+local os = require "os"
+local nixio = require "nixio", require "nixio.util"
+local fs = require "nixio.fs"
+local util = require "luci.util"
+local ltn12 = require "luci.ltn12"
+local srv = require "luci.lucid.http.server"
+local string = require "string"
+
+local prot = require "luci.http.protocol"
+local date = require "luci.http.protocol.date"
+local mime = require "luci.http.protocol.mime"
+local cond = require "luci.http.protocol.conditionals"
+
+module "luci.lucid.http.handler.file"
+
+Simple = util.class(srv.Handler)
+
+function Simple.__init__(self, name, docroot, options)
+       srv.Handler.__init__(self, name)
+       self.docroot = docroot
+       self.realdocroot = fs.realpath(self.docroot)
+
+       options = options or {}
+       self.dirlist = not options.noindex
+       self.error404 = options.error404
+end
+
+function Simple.parse_range(self, request, size)
+       if not request.headers.Range then
+               return true
+       end
+
+       local from, to = request.headers.Range:match("bytes=([0-9]*)-([0-9]*)")
+       if not (from or to) then
+               return true
+       end
+
+       from, to = tonumber(from), tonumber(to)
+       if not (from or to) then
+               return true
+       elseif not from then
+               from, to = size - to, size - 1
+       elseif not to then
+               to = size - 1
+       end
+
+       -- Not satisfiable
+       if from >= size then
+               return false
+       end
+
+       -- Normalize
+       if to >= size then
+               to = size - 1
+       end
+
+       local range = "bytes " .. from .. "-" .. to .. "/" .. size
+       return from, (1 + to - from), range
+end
+
+function Simple.getfile(self, uri)
+       if not self.realdocroot then
+               self.realdocroot = fs.realpath(self.docroot)
+       end
+       local file = fs.realpath(self.docroot .. uri)
+       if not file or file:sub(1, #self.realdocroot) ~= self.realdocroot then
+               return uri
+       end
+       return file, fs.stat(file)
+end
+
+function Simple.handle_GET(self, request)
+       local file, stat = self:getfile(prot.urldecode(request.env.PATH_INFO, true))
+
+       if stat then
+               if stat.type == "reg" then
+
+                       -- Generate Entity Tag
+                       local etag = cond.mk_etag( stat )
+
+                       -- Check conditionals
+                       local ok, code, hdrs
+
+                       ok, code, hdrs = cond.if_modified_since( request, stat )
+                       if ok then
+                               ok, code, hdrs = cond.if_match( request, stat )
+                               if ok then
+                                       ok, code, hdrs = cond.if_unmodified_since( request, stat )
+                                       if ok then
+                                               ok, code, hdrs = cond.if_none_match( request, stat )
+                                               if ok then
+                                                       local f, err = nixio.open(file)
+
+                                                       if f then
+                                                               local code = 200
+                                                               local o, s, r = self:parse_range(request, stat.size)
+
+                                                               if not o then
+                                                                       return self:failure(416, "Invalid Range")
+                                                               end
+
+                                                               local headers = {
+                                                                       ["Last-Modified"]  = date.to_http( stat.mtime ),
+                                                                       ["Content-Type"]   = mime.to_mime( file ),
+                                                                       ["ETag"]           = etag,
+                                                                       ["Accept-Ranges"]  = "bytes",
+                                                               }
+
+                                                               if o == true then
+                                                                       s = stat.size
+                                                               else
+                                                                       code = 206
+                                                                       headers["Content-Range"] = r
+                                                                       f:seek(o)
+                                                               end
+                                                               
+                                                               headers["Content-Length"] = s
+
+                                                               -- Send Response
+                                                               return code, headers, srv.IOResource(f, s)
+                                                       else
+                                                               return self:failure( 403, err:gsub("^.+: ", "") )
+                                                       end
+                                               else
+                                                       return code, hdrs
+                                               end
+                                       else
+                                               return code, hdrs
+                                       end
+                               else
+                                       return code, hdrs
+                               end
+                       else
+                               return code, hdrs
+                       end
+
+               elseif stat.type == "dir" then
+
+                       local ruri = request.env.REQUEST_URI:gsub("/$", "")
+                       local duri = prot.urldecode( ruri, true )
+                       local root = self.docroot
+
+                       -- check for index files
+                       local index_candidates = {
+                               "index.html", "index.htm", "default.html", "default.htm",
+                               "index.txt", "default.txt"
+                       }
+
+                       -- try to find an index file and redirect to it
+                       for i, candidate in ipairs( index_candidates ) do
+                               local istat = fs.stat(
+                                       root .. "/" .. duri .. "/" .. candidate
+                               )
+
+                               if istat ~= nil and istat.type == "reg" then
+                                       return 302, { Location = ruri .. "/" .. candidate }
+                               end
+                       end
+
+
+                       local html = string.format(
+                               '<?xml version="1.0" encoding="utf-8"?>\n' ..
+                               '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '     ..
+                                       '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'..
+                               '<html xmlns="http://www.w3.org/1999/xhtml" '                           ..
+                                       'xml:lang="en" lang="en">\n'                                                    ..
+                               '<head>\n'                                                                                                      ..
+                               '<title>Index of %s/</title>\n'                                                         ..
+                               '<style type="text/css">\n'                                                                     ..
+                                       'body { color:#000000 } '                                                               ..
+                                       'li { border-bottom:1px dotted #CCCCCC; padding:3px } ' ..
+                                       'small { font-size:60%%; color:#333333 } '                              ..
+                                       'p { margin:0 }'                                                                                ..
+                                       '\n</style></head><body><h1>Index of %s/</h1><hr /><ul>'..
+                                       '<li><p><a href="%s/../">../</a> '                                              ..
+                                       '<small>(parent directory)</small><br />'                                               ..
+                                       '<small></small></li>',
+                                       duri, duri, ruri 
+                       )
+
+                       local entries = fs.dir( file )
+
+                       if type(entries) == "function" then
+                               for i, e in util.vspairs(nixio.util.consume(entries)) do
+                                       local estat = fs.stat( file .. "/" .. e )
+
+                                       if estat.type == "dir" then
+                                               html = html .. string.format(
+                                                       '<li><p><a href="%s/%s/">%s/</a> '           ..
+                                                       '<small>(directory)</small><br />'           ..
+                                                       '<small>Changed: %s</small></li>',
+                                                               ruri, prot.urlencode( e ), e,
+                                                               date.to_http( estat.mtime )
+                                               )
+                                       else
+                                               html = html .. string.format(
+                                                       '<li><p><a href="%s/%s">%s</a> '             ..
+                                                       '<small>(%s)</small><br />'                  ..
+                                                       '<small>Size: %i Bytes | '                   ..
+                                                               'Changed: %s</small></li>',
+                                                               ruri, prot.urlencode( e ), e,
+                                                               mime.to_mime( e ),
+                                                               estat.size, date.to_http( estat.mtime )
+                                               )
+                                       end
+                               end
+
+                               html = html .. '</ul><hr /><address>LuCId-HTTPd' .. 
+                                       '</address></body></html>'
+
+                               return 200, {
+                                               ["Date"]         = date.to_http( os.time() );
+                                               ["Content-Type"] = "text/html; charset=utf-8";
+                                       }, ltn12.source.string(html)
+                       else
+                               return self:failure(403, "Permission denied")
+                       end
+               else
+                       return self:failure(403, "Unable to transmit " .. stat.type .. " " .. file)
+               end
+       else
+               if self.error404 then
+                       return 302, { Location = self.error404 }
+               else
+                       return self:failure(404, "No such file: " .. file)
+               end
+       end
+end
+
+function Simple.handle_HEAD(self, ...)
+       local stat, head = self:handle_GET(...)
+       return stat, head
+end
diff --git a/libs/lucid-http/luasrc/lucid/http/handler/luci.lua b/libs/lucid-http/luasrc/lucid/http/handler/luci.lua
new file mode 100644 (file)
index 0000000..c54e393
--- /dev/null
@@ -0,0 +1,96 @@
+--[[
+LuCId HTTP-Slave
+(c) 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 dsp = require "luci.dispatcher"
+local util = require "luci.util"
+local http = require "luci.http"
+local ltn12 = require "luci.ltn12"
+local srv = require "luci.lucid.http.server"
+local coroutine = require "coroutine"
+local type = type
+
+module "luci.lucid.http.handler.luci"
+
+Luci = util.class(srv.Handler)
+
+function Luci.__init__(self, name, prefix)
+       srv.Handler.__init__(self, name)
+       self.prefix = prefix
+end
+
+function Luci.handle_HEAD(self, ...)
+       local stat, head = self:handle_GET(...)
+       return stat, head
+end
+
+function Luci.handle_POST(self, ...)
+       return self:handle_GET(...)
+end
+
+function Luci.handle_GET(self, request, sourcein)
+       local r = http.Request(
+               request.env,
+               sourcein
+       )
+
+       local res, id, data1, data2 = true, 0, nil, nil
+       local headers = {}
+       local status = 200
+       local active = true
+
+       local x = coroutine.create(dsp.httpdispatch)
+       while not id or id < 3 do
+               res, id, data1, data2 = coroutine.resume(x, r, self.prefix)
+
+               if not res then
+                       status = 500
+                       headers["Content-Type"] = "text/plain"
+                       return status, headers, ltn12.source.string(id)
+               end
+
+               if id == 1 then
+                       status = data1
+               elseif id == 2 then
+                       if not headers[data1] then
+                               headers[data1] = data2
+                       elseif type(headers[data1]) ~= "table" then
+                               headers[data1] = {headers[data1], data2}
+                       else
+                               headers[data1][#headers[data1]+1] = data2
+                       end
+               end
+       end
+       
+       if id == 6 then
+               while (coroutine.resume(x)) do end
+               return status, headers, srv.IOResource(data1, data2)
+       end
+
+       local function iter()
+               local res, id, data = coroutine.resume(x)
+               if not res then
+                       return nil, id
+               elseif not id or not active then
+                       return true
+               elseif id == 5 then
+                       active = false
+                       while (coroutine.resume(x)) do end
+                       return nil
+               elseif id == 4 then
+                       return data
+               end
+       end
+
+       return status, headers, iter
+end
+
diff --git a/libs/lucid-http/luasrc/lucid/http/server.lua b/libs/lucid-http/luasrc/lucid/http/server.lua
new file mode 100644 (file)
index 0000000..f5de4e9
--- /dev/null
@@ -0,0 +1,522 @@
+--[[
+LuCId HTTP-Slave
+(c) 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 ipairs, pairs = ipairs, pairs
+local tostring, tonumber = tostring, tonumber
+local pcall, assert, type = pcall, assert, type
+
+local os = require "os"
+local nixio = require "nixio"
+local util = require "luci.util"
+local ltn12 = require "luci.ltn12"
+local proto = require "luci.http.protocol"
+local table = require "table"
+local date = require "luci.http.protocol.date"
+
+module "luci.lucid.http.server"
+
+VERSION = "1.0"
+
+statusmsg = {
+       [200] = "OK",
+       [206] = "Partial Content",
+       [301] = "Moved Permanently",
+       [302] = "Found",
+       [304] = "Not Modified",
+       [400] = "Bad Request",
+       [401] = "Unauthorized",
+       [403] = "Forbidden",
+       [404] = "Not Found",
+       [405] = "Method Not Allowed",
+       [408] = "Request Time-out",
+       [411] = "Length Required",
+       [412] = "Precondition Failed",
+       [416] = "Requested range not satisfiable",
+       [500] = "Internal Server Error",
+       [503] = "Server Unavailable",
+}
+
+-- File Resource
+IOResource = util.class()
+
+function IOResource.__init__(self, fd, len)
+       self.fd, self.len = fd, len
+end
+
+
+-- Server handler implementation
+Handler = util.class()
+
+function Handler.__init__(self, name)
+       self.name = name or tostring(self)
+end
+
+-- Creates a failure reply
+function Handler.failure(self, code, msg)      
+       return code, { ["Content-Type"] = "text/plain" }, ltn12.source.string(msg)
+end
+
+-- Access Restrictions
+function Handler.restrict(self, restriction)
+       if not self.restrictions then
+               self.restrictions = {restriction}
+       else
+               self.restrictions[#self.restrictions+1] = restriction
+       end
+end
+
+-- Check restrictions
+function Handler.checkrestricted(self, request)
+       if not self.restrictions then
+               return
+       end
+
+       local localif, user, pass
+       
+       for _, r in ipairs(self.restrictions) do
+               local stat = true
+               if stat and r.interface then    -- Interface restriction
+                       if not localif then
+                               for _, v in ipairs(request.server.interfaces) do
+                                       if v.addr == request.env.SERVER_ADDR then
+                                               localif = v.name
+                                               break
+                                       end
+                               end
+                       end
+                       
+                       if r.interface ~= localif then
+                               stat = false
+                       end
+               end
+               
+               if stat and r.user then -- User restriction
+                       local rh, pwe
+                       if not user then
+                               rh = (request.headers.Authorization or ""):match("Basic (.*)")
+                               rh = rh and nixio.bin.b64decode(rh) or ""
+                               user, pass = rh:match("(.*):(.*)")
+                               pass = pass or ""
+                       end
+                       pwe = nixio.getsp and nixio.getsp(r.user) or nixio.getpw(r.user)
+                       local pwh = (user == r.user) and pwe and (pwe.pwdp or pwe.passwd)
+                       if not pwh or #pwh < 1 or nixio.crypt(pass, pwh) ~= pwh then
+                               stat = false
+                       end
+               end
+               
+               if stat then
+                       return
+               end
+       end
+       
+       return 401, {
+               ["WWW-Authenticate"] = ('Basic realm=%q'):format(self.name),
+               ["Content-Type"] = 'text/plain'
+       }, ltn12.source.string("Unauthorized")
+end
+
+-- Processes a request
+function Handler.process(self, request, sourcein)
+       local stat, code, hdr, sourceout
+       
+       local stat, code, msg = self:checkrestricted(request)
+       if stat then    -- Access Denied
+               return stat, code, msg
+       end
+
+       -- Detect request Method
+       local hname = "handle_" .. request.env.REQUEST_METHOD
+       if self[hname] then
+               -- Run the handler
+               stat, code, hdr, sourceout = pcall(self[hname], self, request, sourcein)
+
+               -- Check for any errors
+               if not stat then
+                       return self:failure(500, code)
+               end
+       else
+               return self:failure(405, statusmsg[405])
+       end
+
+       return code, hdr, sourceout
+end
+
+
+VHost = util.class()
+
+function VHost.__init__(self)
+       self.handlers = {}
+end
+
+function VHost.process(self, request, ...)
+       local handler
+       local hlen = -1
+       local uri = request.env.SCRIPT_NAME
+       local sc = ("/"):byte()
+
+       -- SCRIPT_NAME
+       request.env.SCRIPT_NAME = ""
+
+       -- Call URI part
+       request.env.PATH_INFO = uri
+       
+       for k, h in pairs(self.handlers) do
+               if #k > hlen then
+                       if uri == k or (uri:sub(1, #k) == k and uri:byte(#k+1) == sc) then
+                               handler = h
+                               hlen = #k
+                               request.env.SCRIPT_NAME = k
+                               request.env.PATH_INFO   = uri:sub(#k+1)
+                       end
+               end
+       end
+       
+       if handler then
+               return handler:process(request, ...)
+       else
+               return 404, nil, ltn12.source.string("No such handler")
+       end
+end
+
+function VHost.get_handlers(self)
+       return self.handlers
+end
+
+function VHost.set_handler(self, match, handler)
+       self.handlers[match] = handler
+end
+
+
+local function remapipv6(adr)
+       local map = "::ffff:"
+       if adr:sub(1, #map) == map then
+               return adr:sub(#map+1)
+       else
+               return adr
+       end 
+end
+
+local function chunksource(sock, buffer)
+       buffer = buffer or ""
+       return function()
+               local output
+               local _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
+               while not count and #buffer <= 1024 do
+                       local newblock, code = sock:recv(1024 - #buffer)
+                       if not newblock then
+                               return nil, code
+                       end
+                       buffer = buffer .. newblock  
+                       _, endp, count = buffer:find("^([0-9a-fA-F]+);?.-\r\n")
+               end
+               count = tonumber(count, 16)
+               if not count then
+                       return nil, -1, "invalid encoding"
+               elseif count == 0 then
+                       return nil
+               elseif count + 2 <= #buffer - endp then
+                       output = buffer:sub(endp+1, endp+count)
+                       buffer = buffer:sub(endp+count+3)
+                       return output
+               else
+                       output = buffer:sub(endp+1, endp+count)
+                       buffer = ""
+                       if count - #output > 0 then
+                               local remain, code = sock:recvall(count-#output)
+                               if not remain then
+                                       return nil, code
+                               end
+                               output = output .. remain
+                               count, code = sock:recvall(2)
+                       else
+                               count, code = sock:recvall(count+2-#buffer+endp)
+                       end
+                       if not count then
+                               return nil, code
+                       end
+                       return output
+               end
+       end
+end
+
+local function chunksink(sock)
+       return function(chunk, err)
+               if not chunk then
+                       return sock:writeall("0\r\n\r\n")
+               else
+                       return sock:writeall(("%X\r\n%s\r\n"):format(#chunk, chunk))
+               end
+       end
+end
+
+Server = util.class()
+
+function Server.__init__(self)
+       self.vhosts = {}
+end
+
+function Server.get_vhosts(self)
+       return self.vhosts
+end
+
+function Server.set_vhost(self, name, vhost)
+       self.vhosts[name] = vhost
+end
+
+function Server.error(self, client, code, msg)
+       hcode = tostring(code)
+       
+       client:writeall( "HTTP/1.0 " .. hcode .. " " ..
+        statusmsg[code] .. "\r\n" )
+       client:writeall( "Connection: close\r\n" )
+       client:writeall( "Content-Type: text/plain\r\n\r\n" )
+
+       if msg then
+               client:writeall( "HTTP-Error " .. code .. ": " .. msg .. "\r\n" )
+       end
+       
+       client:close()
+end
+
+local hdr2env = {
+       ["Content-Length"] = "CONTENT_LENGTH",
+       ["Content-Type"] = "CONTENT_TYPE",
+       ["Content-type"] = "CONTENT_TYPE",
+       ["Accept"] = "HTTP_ACCEPT",
+       ["Accept-Charset"] = "HTTP_ACCEPT_CHARSET",
+       ["Accept-Encoding"] = "HTTP_ACCEPT_ENCODING",
+       ["Accept-Language"] = "HTTP_ACCEPT_LANGUAGE",
+       ["Connection"] = "HTTP_CONNECTION",
+       ["Cookie"] = "HTTP_COOKIE",
+       ["Host"] = "HTTP_HOST",
+       ["Referer"] = "HTTP_REFERER",
+       ["User-Agent"] = "HTTP_USER_AGENT"
+}
+
+function Server.parse_headers(self, source)
+       local env = {}
+       local req = {env = env, headers = {}}
+       local line, err
+
+       repeat  -- Ignore empty lines
+               line, err = source()
+               if not line then
+                       return nil, err
+               end
+       until #line > 0
+       
+       env.REQUEST_METHOD, env.REQUEST_URI, env.SERVER_PROTOCOL =
+               line:match("^([A-Z]+) ([^ ]+) (HTTP/1%.[01])$")
+               
+       if not env.REQUEST_METHOD then
+               return nil, "invalid magic"
+       end
+       
+       local key, envkey, val
+       repeat
+               line, err = source()
+               if not line then
+                       return nil, err
+               elseif #line > 0 then   
+                       key, val = line:match("^([%w-]+)%s?:%s?(.*)")
+                       if key then
+                               req.headers[key] = val
+                               envkey = hdr2env[key]
+                               if envkey then
+                                       env[envkey] = val
+                               end
+                       else
+                               return nil, "invalid header line"
+                       end
+               else
+                       break
+               end
+       until false
+       
+       env.SCRIPT_NAME, env.QUERY_STRING = env.REQUEST_URI:match("(.*)%??(.*)")
+       return req
+end
+
+
+function Server.process(self, client, env)
+       local sourcein  = function() end
+       local sourcehdr = client:linesource()
+       local sinkout
+       local buffer
+       
+       local close = false
+       local stat, code, msg, message, err
+       
+       client:setsockopt("socket", "rcvtimeo", 15)
+       client:setsockopt("socket", "sndtimeo", 15)
+       
+       repeat
+               -- parse headers
+               message, err = self:parse_headers(sourcehdr)
+
+               -- any other error
+               if not message or err then
+                       if err == 11 then       -- EAGAIN
+                               break
+                       else
+                               return self:error(client, 400, err)
+                       end
+               end
+
+               -- Prepare sources and sinks
+               buffer = sourcehdr(true)
+               sinkout = client:sink()
+               message.server = env
+               
+               if client:is_tls_socket() then
+                       message.env.HTTPS = "on"
+               end
+               
+               -- Addresses
+               message.env.REMOTE_ADDR = remapipv6(env.host)
+               message.env.REMOTE_PORT = env.port
+               
+               local srvaddr, srvport = client:getsockname()
+               message.env.SERVER_ADDR = remapipv6(srvaddr)
+               message.env.SERVER_PORT = srvport
+               
+               -- keep-alive
+               if message.env.SERVER_PROTOCOL == "HTTP/1.1" then
+                       close = (message.env.HTTP_CONNECTION == "close")
+               else
+                       close = not message.env.HTTP_CONNECTION 
+                               or message.env.HTTP_CONNECTION == "close"
+               end
+
+               -- Uncomment this to disable keep-alive
+               close = close or env.config.nokeepalive
+       
+               if message.env.REQUEST_METHOD == "GET"
+               or message.env.REQUEST_METHOD == "HEAD" then
+                       -- Be happy
+                       
+               elseif message.env.REQUEST_METHOD == "POST" then
+                       -- If we have a HTTP/1.1 client and an Expect: 100-continue header
+                       -- respond with HTTP 100 Continue message
+                       if message.env.SERVER_PROTOCOL == "HTTP/1.1" 
+                       and message.headers.Expect == '100-continue' then
+                               client:writeall("HTTP/1.1 100 Continue\r\n\r\n")
+                       end
+                       
+                       if message.headers['Transfer-Encoding'] and
+                        message.headers['Transfer-Encoding'] ~= "identity" then
+                               sourcein = chunksource(client, buffer)
+                               buffer = nil
+                       elseif message.env.CONTENT_LENGTH then
+                               local len = tonumber(message.env.CONTENT_LENGTH)
+                               if #buffer >= len then
+                                       sourcein = ltn12.source.string(buffer:sub(1, len))
+                                       buffer = buffer:sub(len+1)
+                               else
+                                       sourcein = ltn12.source.cat(
+                                               ltn12.source.string(buffer),
+                                               client:blocksource(nil, len - #buffer)
+                                       )
+                               end
+                       else
+                               return self:error(client, 411, statusmsg[411])
+                       end
+               else
+                       return self:error(client, 405, statusmsg[405])
+               end
+
+
+               local host = self.vhosts[message.env.HTTP_HOST] or self.vhosts[""]
+               if not host then
+                       return self:error(client, 404, "No virtual host found")
+               end
+               
+               local code, headers, sourceout = host:process(message, sourcein)
+               headers = headers or {}
+               
+               -- Post process response
+               if sourceout then
+                       if util.instanceof(sourceout, IOResource) then
+                               if not headers["Content-Length"] then
+                                       headers["Content-Length"] = sourceout.len
+                               end
+                       end
+                       if not headers["Content-Length"] then
+                               if message.http_version == 1.1 then
+                                       headers["Transfer-Encoding"] = "chunked"
+                                       sinkout = chunksink(client)
+                               else
+                                       close = true
+                               end
+                       end
+               elseif message.request_method ~= "head" then
+                       headers["Content-Length"] = 0
+               end
+               
+               if close then
+                       headers["Connection"] = "close"
+               elseif message.env.SERVER_PROTOCOL == "HTTP/1.0" then
+                       headers["Connection"] = "Keep-Alive"
+               end 
+
+               headers["Date"] = date.to_http(os.time())
+               local header = {
+                       message.env.SERVER_PROTOCOL .. " " .. tostring(code) .. " " 
+                               .. statusmsg[code],
+                       "Server: LuCId-HTTPd/" .. VERSION
+               }
+
+               
+               for k, v in pairs(headers) do
+                       if type(v) == "table" then
+                               for _, h in ipairs(v) do
+                                       header[#header+1] = k .. ": " .. h
+                               end
+                       else
+                               header[#header+1] = k .. ": " .. v
+                       end
+               end
+
+               header[#header+1] = ""
+               header[#header+1] = ""
+               
+               -- Output
+               stat, code, msg = client:writeall(table.concat(header, "\r\n"))
+
+               if sourceout and stat then
+                       if util.instanceof(sourceout, IOResource) then
+                               stat, code, msg = sourceout.fd:copyz(client, sourceout.len)
+                       else
+                               stat, msg = ltn12.pump.all(sourceout, sinkout)
+                       end
+               end
+
+
+               -- Write errors
+               if not stat then
+                       if msg then
+                               nixio.syslog("err", "Error sending data to " .. env.host ..
+                                       ": " .. msg .. "\n")
+                       end
+                       break
+               end
+               
+               if buffer then
+                       sourcehdr(buffer)
+               end
+       until close
+       
+       client:shutdown()
+       client:close()
+end
diff --git a/libs/lucid/Makefile b/libs/lucid/Makefile
new file mode 100644 (file)
index 0000000..f7fac77
--- /dev/null
@@ -0,0 +1,2 @@
+include ../../build/config.mk
+include ../../build/module.mk
diff --git a/libs/lucid/hostfiles/etc/config/lucid b/libs/lucid/hostfiles/etc/config/lucid
new file mode 100644 (file)
index 0000000..5a732ac
--- /dev/null
@@ -0,0 +1,61 @@
+config lucid main
+       option pollinterval 15000
+       option daemon 1
+       option debug 1
+       list supports tcpserver
+       list supports server
+
+config DirectoryPublisher webroot
+       option name 'Webserver Share'
+       option physical host/www
+       option virtual ''
+       option domain ''
+       list read ':lo'
+       list read ':br-lan'
+       list read 'root'
+       
+config LuciWebPublisher luciweb
+       option name 'LuCI Webapplication'
+       option physical ''
+       list virtual /luci
+       option domain ''
+       list exec ':lo'
+       list exec ':br-lan'
+       list exec 'root'
+       
+config RPCPublisher    mainrpc
+       option namespace 'luci.lucid.rpc'
+       list export system
+       list export ruci
+       list exec ':lo'
+       list exec 'root'
+
+config tcpserver httpd
+       option entrypoint "luci.lucid.http"
+       list supports DirectoryPublisher
+       list supports LuciWebPublisher
+       
+config tcpserver rpcd
+       option entrypoint "luci.lucid.rpc"
+       list supports RPCPublisher
+
+config daemon http
+       option slave httpd
+       list address 8080
+       list publisher webroot
+       list publisher luciweb
+       option enabled 1
+       
+config daemon https
+       option slave httpd
+       list address 4443
+       list publisher webroot
+       list publisher luciweb
+       option enabled 1
+       option encryption enable
+       
+config daemon rpc
+       option slave rpcd
+       list address 12900
+       list publisher mainrpc
+       option enabled 1
\ No newline at end of file
diff --git a/libs/lucid/luasrc/lucid.lua b/libs/lucid/luasrc/lucid.lua
new file mode 100644 (file)
index 0000000..62741e7
--- /dev/null
@@ -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 (file)
index 0000000..22f0945
--- /dev/null
@@ -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
diff --git a/libs/lucid/root/etc/config/lucid b/libs/lucid/root/etc/config/lucid
new file mode 100644 (file)
index 0000000..dda68d9
--- /dev/null
@@ -0,0 +1,61 @@
+config lucid main
+       option pollinterval 15000
+       option threadlimit 25
+       option daemon 1
+       option debug 1
+       list supports tcpserver
+       list supports server
+
+config DirectoryPublisher webroot
+       option name 'Webserver Share'
+       option physical /www
+       option virtual /
+       option domain ''
+       list read ':lo'
+       list read ':br-lan'
+       list read 'root'
+       
+config LuciWebPublisher luciweb
+       option name 'LuCI Webapplication'
+       option physical ''
+       list virtual /luci
+       option domain ''
+       list exec ':lo'
+       list exec ':br-lan'
+       list exec 'root'
+       
+config RPCPublisher    mainrpc
+       option namespace 'luci.lucid.rpc'
+       list export system
+       list exec ':lo'
+       list exec 'root'
+
+config tcpserver httpd
+       option entrypoint "luci.lucid.http"
+       list supports DirectoryPublisher
+       list supports LuciWebPublisher
+       
+config tcpserver rpcd
+       option entrypoint "luci.lucid.rpc"
+       list supports RPCPublisher
+
+config daemon http
+       option slave httpd
+       list address 8080
+       list publisher webroot
+       list publisher luciweb
+       option enabled 1
+       
+config daemon https
+       option slave httpd
+       list address 4443
+       list publisher webroot
+       list publisher luciweb
+       option enabled 1
+       option encryption enable
+       
+config daemon rpc
+       option slave rpcd
+       list address 12900
+       list publisher mainrpc
+       option enabled 1
\ No newline at end of file
index a210ca0..a21f2a2 100644 (file)
@@ -8,3 +8,4 @@ lkc_defs.h
 mconf
 zconf.tab.h
 zconf.tab.c
+src/nixio.dll
index f745ffe..35d20b2 100644 (file)
@@ -79,7 +79,7 @@ function copy(src, dest)
                end
        elseif stat.type == "lnk" then
                res, code, msg = nixio.symlink(nixio.readlink(src), dest)
-       else
+       elseif stat.type == "reg" then
                res, code, msg = datacopy(src, dest)
        end
        
index 2c9fc93..6f92951 100644 (file)
@@ -14,7 +14,7 @@ $Id$
 
 local table = require "table"
 local nixio = require "nixio"
-local getmetatable, assert, pairs = getmetatable, assert, pairs
+local getmetatable, assert, pairs, type = getmetatable, assert, pairs, type
 
 module "nixio.util"
 
@@ -106,7 +106,7 @@ function meta.linesource(self, limit)
                
                if flush then
                        line = buffer:sub(bpos + 1)
-                       buffer = ""
+                       buffer = type(flush) == "string" and flush or ""
                        bpos = 0
                        return line
                end
@@ -189,8 +189,11 @@ end
 
 function meta.copyz(self, fd, size)
        local sent, lsent, code, msg = 0
+       local splicable
+
        if self:is_file() then
-               if nixio.sendfile and fd:is_socket() and self:stat("type") == "reg" then
+               local ftype = self:stat("type")
+               if nixio.sendfile and fd:is_socket() and ftype == "reg" then
                        repeat
                                lsent, code, msg = nixio.sendfile(fd, self, size or ZIOBLKSIZE)
                                if lsent then
@@ -202,7 +205,27 @@ function meta.copyz(self, fd, size)
                         code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then
                                return lsent and sent, code, msg, sent
                        end
-               end 
+               elseif nixio.splice and not fd:is_tls_socket() and ftype == "fifo" then 
+                       splicable = true
+               end
+       end
+
+       if nixio.splice and fd:is_file() and not splicable then
+               splicable = not self:is_tls_socket() and fd:stat("type") == "fifo"
+       end
+
+       if splicable then
+               repeat
+                       lsent, code, msg = nixio.splice(self, fd, size or ZIOBLKSIZE)
+                       if lsent then
+                               sent = sent + lsent
+                               size = size and (size - lsent)
+                       end
+               until (not lsent or lsent == 0 or (size and size == 0))
+               if lsent or (not lsent and sent == 0 and
+                code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then
+                       return lsent and sent, code, msg, sent
+               end             
        end
 
        return self:copy(fd, size)
@@ -212,6 +235,24 @@ function tls_socket.close(self)
        return self.socket:close()
 end
 
+function tls_socket.getsockname(self)
+       return self.socket:getsockname()
+end
+
+function tls_socket.getpeername(self)
+       return self.socket:getpeername()
+end
+
+function tls_socket.getsockopt(self, ...)
+       return self.socket:getsockopt(...)
+end
+tls_socket.getopt = tls_socket.getsockopt
+
+function tls_socket.setsockopt(self, ...)
+       return self.socket:setsockopt(...)
+end
+tls_socket.setopt = tls_socket.setsockopt
+
 for k, v in pairs(meta) do
        file[k] = v
        socket[k] = v