summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile47
-rw-r--r--lib/README3
-rw-r--r--lib/ed.lua60
-rw-r--r--lib/fs.lua89
-rw-r--r--lib/html.lua245
-rw-r--r--lib/join.lua25
-rw-r--r--lib/log_view.lua56
-rw-r--r--lib/menubuilder.lua150
-rw-r--r--lib/service_controller.lua189
-rw-r--r--lib/service_model.lua115
-rw-r--r--lib/session.lua143
-rw-r--r--lib/split.lua31
-rwxr-xr-xlib/validator.lua156
-rw-r--r--lib/web_elements.lua156
14 files changed, 1465 insertions, 0 deletions
diff --git a/lib/Makefile b/lib/Makefile
new file mode 100644
index 0000000..0c65ca1
--- /dev/null
+++ b/lib/Makefile
@@ -0,0 +1,47 @@
+include ../config.mk
+
+LIB_DIST=ed.lua\
+ fs.lua\
+ html.lua\
+ join.lua\
+ log_view.lua\
+ menubuilder.lua\
+ service_controller.lua\
+ service_model.lua\
+ session.lua\
+ split.lua\
+ validator.lua\
+ web_elements.lua
+
+EXTRA_DIST=README Makefile
+DISTFILES=$(LIB_DIST) $(EXTRA_DIST)
+
+install_dir=$(DESTDIR)/$(acflibdir)
+dist_dir=$(DISTDIR)/$(notdir $(PWD))
+
+phony+=all
+all:
+
+phony+=clean
+clean:
+
+phony+=distdir
+distdir: $(DISTFILES)
+ mkdir -p "$(dist_dir)"
+ for i in $(DISTFILES); do\
+ dest=`dirname "$(dist_dir)/$$i"`;\
+ mkdir -p "$$dest";\
+ cp "$$i" "$$dest";\
+ done
+
+phony+=install
+install:
+ mkdir -p $(install_dir)
+ for i in $(LIB_DIST); do\
+ dest=`dirname "$(install_dir)/$$i"`;\
+ mkdir -p "$$dest";\
+ cp "$$i" "$$dest";\
+ done
+
+
+.PHONY: $(phony)
diff --git a/lib/README b/lib/README
new file mode 100644
index 0000000..cbc9798
--- /dev/null
+++ b/lib/README
@@ -0,0 +1,3 @@
+In the process of cleaning up...
+shell libs are no longer used, just keeping them for historical reference at this point
+lua libs are being worked on.
diff --git a/lib/ed.lua b/lib/ed.lua
new file mode 100644
index 0000000..2e86f08
--- /dev/null
+++ b/lib/ed.lua
@@ -0,0 +1,60 @@
+#!/usr/bin/lua
+
+require "object"
+
+-- ed object
+Ed = Object:new{
+ filename = nil,
+ lines = {}
+}
+
+
+-- openfile and read it to table
+function Ed:open( filename, mode )
+ local f = io.open( filename, mode )
+ -- check that open was success
+ if f == nil then
+ return nil
+ end
+
+ -- read the lines
+ for line in f:lines() do
+ table.insert( self.lines, line )
+ end
+ f:close()
+ self.filename = filename
+ return self.lines
+end
+
+
+-- search and replace on lines that matches linematch
+function Ed:find_gsub( linematch, search, replace, limit )
+ local i, line
+ for i, line in ipairs( self.lines ) do
+ if string.find( line, linematch ) then
+ self.lines[i] = string.gsub( line, search, replace, limit )
+ end
+ end
+end
+
+
+-- Write the table to file again
+function Ed:flush( filename )
+ local f = io.open( filename, "w" )
+ if f == nil then
+ return false
+ end
+
+ -- write each line to file
+ for i, line in ipairs( self.lines ) do
+ f:write(line .. "\n") -- test this!
+ end
+ f:close()
+ return true
+end
+
+function Ed:insert( line )
+ self.lines:insert( line )
+end
+
+
diff --git a/lib/fs.lua b/lib/fs.lua
new file mode 100644
index 0000000..d171074
--- /dev/null
+++ b/lib/fs.lua
@@ -0,0 +1,89 @@
+--[[
+ module for generic filesystem funcs
+
+ Copyright (c) Natanael Copa 2006
+
+]]--
+
+module (..., package.seeall)
+
+require ("lfs")
+
+-- generic wrapper funcs
+function is_dir ( pathstr )
+ return lfs.attributes ( pathstr, "mode" ) == "directory"
+end
+
+function is_file ( pathstr )
+ return lfs.attributes ( pathstr, "mode" ) == "file"
+end
+
+-- Returns the contents of a file as a string
+function read_file ( path )
+ local file = io.open(path)
+ if ( file ) then
+ local f = file:read("*a")
+ file:close()
+ return f
+ else
+ return nil
+ end
+end
+
+-- Returns an array with the contents of a file,
+-- or nil and the error message
+function read_file_as_array ( path )
+ local file, error = io.open(path)
+ if ( file == nil ) then
+ return nil, error
+ end
+ local f = {}
+ for line in file:lines() do
+ table.insert ( f , line )
+ end
+ file:close()
+ return f
+end
+
+
+
+
+-- write a string to a file
+function write_file ( path, str )
+ local file = io.open(path, "w")
+ if ( file ) then
+ file:write(str)
+ file:close()
+ end
+end
+
+
+-- iterator function for finding dir entries matching filespec (what)
+-- starting at where, or currentdir if not specified.
+-- Finds regexes, not fileglobs
+function find ( what, where )
+ -- returns an array of files under "where" that match what "f"
+ local function find_files_as_array ( f, where, t )
+ where = where or lfs.currentdir()
+ f = f or ".*"
+ t = t or {}
+ for d in lfs.dir ( where ) do
+ if fs.is_dir ( where .. "/" .. d ) and (d ~= ".") and ( d ~= "..") then
+ find_files_as_array (f, where .. "/" .. d, t )
+ end
+ if (string.match (d, "^" .. f .. "$" )) then
+ table.insert (t, ( string.gsub ( where .. "/" .. d, "/+", "/" ) ) )
+ end
+ end
+ return (t)
+ end
+
+ -- This is the iterator
+ local t = find_files_as_array ( what, where )
+ local idx = 0
+ return function ()
+ idx = idx + 1
+ return t[idx]
+ end
+end
+
diff --git a/lib/html.lua b/lib/html.lua
new file mode 100644
index 0000000..7e60c2c
--- /dev/null
+++ b/lib/html.lua
@@ -0,0 +1,245 @@
+--[[ lowlevel html functions
+ Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org
+ Copyright (C) 2007 Nathan Angelacos
+ Licensed under the terms of GPL2
+]]--
+module (..., package.seeall)
+
+--[[ Cookie functions ]]------------------------------------------------------
+cookie={}
+
+-- Set a cookie - returns a string suitable for setting a cookie
+-- if the value is the boolean "false", then set the cookie to expire
+cookie.set = function ( name, value, path )
+ local expires = ""
+ if name == nil then
+ return ("")
+ end
+ if value == false then
+ expires = 'expires=Thu Jan 1 00:00:00 EST 1970'
+ value = ""
+ end
+ if path == nil then
+ path = "/"
+ end
+ return (string.format('Set-Cookie: %s=%s; path=%s; %s\n', tostring(name),
+ tostring(value), path, expires))
+end
+
+
+-- wrapper function to clear a cookie
+cookie.unset = function ( name, path)
+ return cookie.set (name, false, path)
+end
+
+
+
+-- escape unsafe html characters
+function html_escape (text )
+ text = text or ""
+ local str = string.gsub (text, "&", "&" )
+ str = string.gsub (str, "<", "&lt;" )
+ return string.gsub (str, ">", "&gt;" )
+end
+
+-- return a name,value pair as a string.
+nv_pair = function ( name, value)
+ if ( name == nil ) then
+ return ( value or "" )
+ end
+
+ if ( type(value) == "boolean" ) then
+ value = tostring(value)
+ end
+
+ if ( value == nil ) then
+ return ( "" )
+ else
+ return (string.format (' %s="%s" ', name , ( value or "" ) ))
+ end
+end
+
+
+--[[
+ each of these functions take a table that has an associative array of
+ the values we might care about:
+
+ value -- this is the value in the form element, or the selected element
+ name -- this is the name of the element
+ cols, rows
+ class
+ id
+ etc.
+]]--
+
+local generic_input = function ( field_type, v )
+ if type(v.value) == "table" then
+ ret = {}
+ local vals = v.value
+ for n, val in ipairs(vals) do
+ v.value = val
+ table.insert(ret, generic_input(field_type, v))
+ end
+ v.value = vals
+ return table.concat(ret)
+ end
+ if ( field_type == nil ) then
+ return nil
+ end
+
+ local str = string.format ( '<input type="%s" ', field_type )
+
+ for i,k in ipairs ( {
+ "name", "size", "checked", "maxlength",
+ "value", "length", "class", "id", "src",
+ "align", "alt",
+ "tabindex", "accesskey", "onfocus", "onblur"
+ } ) do
+ str = str .. nv_pair ( k, v[k] )
+ end
+
+ if ( v.disabled ~= nil ) then
+ str = str .. " disabled"
+ end
+
+ return ( str .. ">" )
+end
+
+
+--[[ Form functions ]]------------------------------------------------------
+-- These expect something like a cfe to work (see acf_www-controller.lua)
+
+form = {}
+form.text = function ( v )
+ return generic_input ( "text", v )
+end
+
+
+form.longtext = function ( v )
+ local str = "<textarea"
+ for i,k in ipairs ( {
+ "name", "rows", "cols",
+ "class", "id", "tabindex", "accesskey",
+ "onfocus", "onblur"
+ } ) do
+ str = str .. nv_pair ( k, v[k] )
+ end
+ str = str .. nv_pair (nil, v.disabled)
+ return ( str .. ">" .. (v.value or "" ) .. "</textarea>" )
+end
+
+
+function form.passwd ( v )
+ return generic_input ( "password", v )
+end
+
+function form.hidden ( v )
+ return generic_input ( "hidden", v )
+end
+
+
+function form.submit ( v )
+ return generic_input ( "submit", v )
+end
+
+
+function form.action (v)
+ return generic_input ("submit", v)
+end
+
+function form.file ( v )
+ return generic_input ( "file", v )
+end
+
+function form.image ( v )
+ return generic_input ( "image", v )
+end
+
+
+-- v.value is the selected item
+-- v.option is an array of valid options
+-- NOTE use of value and values (plural)
+function form.select ( v )
+ if ( v.name == nil ) then
+ return nil
+ end
+ local str = "<select"
+ for i,k in ipairs ( {
+ "name", "size", "tabindex", "accesskey",
+ "onfocus", "onblur", "onchange", "id",
+ "class"
+ } ) do
+ str = str .. nv_pair ( k, v[k] )
+ end
+
+ if ( v.disabled ~= nil ) then
+ str = str .. " disabled"
+ end
+ str = str .. ">"
+ -- now the options
+ for i, k in ipairs ( v.option ) do
+ local val = k
+ local txt = nil
+ if type(val) == "table" then
+ txt=val[1]
+ val=val[0]
+ end
+ str = str .. "<option "
+ if ( v.value == val ) then
+ str = str .. " selected "
+ end
+ str = str .. nv_pair("value", val) .. ">" .. k .. "</option>"
+ end
+ str = str .. "</select>"
+ return (str)
+end
+
+function form.checkbox ( v )
+ return generic_input ( "checkbox", v )
+end
+
+
+-- NOTE: VALUE of a form is a table containing the form elements ...
+function form.start ( v)
+ if ( v.action == nil ) then
+ return nil
+ end
+
+ local method = v.method or "get"
+ return ( string.format (
+ '<form %s%s%s>',
+ nv_pair ( "class", v.class ),
+ nv_pair ( "method", v.method),
+ nv_pair ( "action", v.action )
+ ) )
+end
+
+function form.stop ( )
+ return ("</form>")
+end
+
+-- For "h1, h2, p," etc
+-- WARNING - Text is printed verbatim - you may want to
+-- wrap the text in html_escape
+function entity (tag, text, class, id)
+ return ( string.format (
+ "<%s%s%s>%s</%s>",
+ tag,
+ nv_pair ("class", class),
+ nv_pair("id", id), text , tag)
+ )
+end
+
+
+function link ( v )
+ if ( v.value == nil ) then
+ return nil
+ end
+ local str = nv_pair ( "href", v.value )
+ for i,k in ipairs( { "class", "id" }) do
+ str = str .. nv_pair ( k, v[k] )
+ end
+
+ return ( "<a " .. str .. ">" .. (v.label or "" ) .. "</a>" )
+end
+
diff --git a/lib/join.lua b/lib/join.lua
new file mode 100644
index 0000000..e3621de
--- /dev/null
+++ b/lib/join.lua
@@ -0,0 +1,25 @@
+--[[
+ module for joining an array to a string
+]]--
+
+module (..., package.seeall)
+
+
+-- This code comes from http://lua-users.org/wiki/SplitJoin
+--
+-- Concat the contents of the parameter list,
+-- -- separated by the string delimiter (just like in perl)
+-- -- example: strjoin(", ", {"Anna", "Bob", "Charlie", "Dolores"})
+return function (delimiter, list)
+ local len = getn(list)
+ if len == 0 then
+ return ""
+ end
+ local string = list[1]
+ for i = 2, len do
+ string = string .. delimiter .. list[i]
+ end
+ return string
+end
+
+
diff --git a/lib/log_view.lua b/lib/log_view.lua
new file mode 100644
index 0000000..f32400e
--- /dev/null
+++ b/lib/log_view.lua
@@ -0,0 +1,56 @@
+require ("web_elements")
+
+local function fwrite(fmt, ...)
+ return io.write(string.format(fmt, ...))
+end
+
+local function footer(time)
+ fwrite("<div id=\"footer\">\n<p>This request was processed in approximately %d seconds</p>\n</div>",time)
+end
+
+header = [[
+content-type: text/html
+
+<!DOCTYPE HTML PUBLIC "-//W3C//ddD HTML 4.01 Transitional//EN">
+<html lang="en">
+
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+<title>Alpine log view</title>
+<link rel="stylesheet" type="text/css" href="/static/webconf.css" />
+<meta http-equiv='Cache-Control' content='no-cache' />
+<Meta http-equiv='Pragma' content='no-cache' />
+</head>]]
+
+--
+
+print(header)
+print("<body>\n<div id=\"head\">")
+
+fwrite("<h1>%s</h1>",cf.hostinfo.alpine_hostname)
+
+fwrite("<p><em>%s</em></p>",cf.hostinfo.alpine_release)
+
+print("</div>")
+
+print ('<div id="mainmenu">')
+local group, cat, subcat =
+ web_elements.render_mainmenu ( menu, cf.prefix, cf.controller, cf.action )
+print([[
+</div>
+
+<div id="submenu">]])
+web_elements.render_submenu ( menu, group, cat, subcat )
+print([[</div>
+
+<div id="content">
+<p>]])
+-- get the wc and view tables
+-- walk the tree
+web_elements.render_table ( view )
+print("</p>\n</div>")
+
+print(footer(cf.time))
+print("</body></html>")
+
+-- /* vim: set filetype=lua : */
diff --git a/lib/menubuilder.lua b/lib/menubuilder.lua
new file mode 100644
index 0000000..6def1fe
--- /dev/null
+++ b/lib/menubuilder.lua
@@ -0,0 +1,150 @@
+--[[ parse through the *.menu tables and return a "menu" table
+ Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org
+ Copyright (C) 2007 Nathan Angelacos
+ Licensed under the terms of GPL2
+ ]]--
+module(..., package.seeall)
+
+-- returns a table of the "*.menu" tables
+-- uses the system "find" command
+-- startdir should be the app dir.
+local get_candidates = function (startdir)
+ local t = {}
+ startdir = startdir .. "/"
+ local fh = io.popen('find ' .. startdir .. ' -name "*.menu"')
+
+ local start = string.gsub(startdir, "/$", "")
+ for x in fh:lines() do
+ table.insert (t, (string.gsub(x, start, "")))
+ end
+
+ return t
+end
+
+
+-- internal function for table.sort
+local t_compare = function (x,y,f)
+ for k,v in pairs(f) do
+ local a = x[v]
+ local b = y[v]
+ if tonumber(a) and tonumber(b) then
+ a=tonumber(a)
+ b=tonumber(b)
+ end
+ if a < b then return true end
+ if a > b then return false end
+ end
+ return false
+ end
+
+
+-- returns a table of all the menu items found, sorted by priority
+-- Table format: prefix controller cat group tab action
+get_menuitems = function (startdir)
+ local t = {}
+ for k,v in pairs(get_candidates(startdir)) do
+ local prefix, controller = mvc.dirname(v), mvc.basename(v, ".menu")
+ -- open the thing, and parse the contents
+ local fh = io.open(startdir .. "/" .. v)
+ local prio = 10
+ for x in fh:lines() do
+ if not string.match(x, "^#") then
+ local item = {}
+ for i in string.gmatch(x, "%S+") do
+ table.insert(item, i)
+ end
+ table.insert(t, { prefix=prefix,
+ controller=controller,
+ catprio="nan",
+ cat=item[1] or "",
+ groupprio="nan",
+ group=item[2] or "",
+ tabprio=tostring(prio),
+ tab=item[3] or "",
+ action=item[4] or "" })
+ prio=prio+5
+ end
+ end
+ fh:close()
+ end
+ -- Ok, we now have the raw menu table
+ -- now try to parse out numbers in front of any cat, group or tabs
+ for x in ipairs(t) do
+ local f = t[x]
+ if (string.match(f.cat, "^%d")) then
+ f.catprio, f.cat = string.match(f.cat, "(%d+)(.*)")
+ end
+ if (string.match(f.group, "^%d")) then
+ f.groupprio, f.group = string.match(f.group, "(%d+)(.*)")
+ end
+ if (string.match(f.tab, "^%d")) then
+ f.tabprio, f.tab = string.match(f.tab, "(%d+)(.*)")
+ end
+ end
+
+ -- Convert underscores to spaces
+ for x in ipairs(t) do
+ t[x].cat = string.gsub(t[x].cat, "_", " ")
+ t[x].group = string.gsub(t[x].group, "_", " ")
+ t[x].tab = string.gsub(t[x].tab, "_", " ")
+ end
+
+ -- Now alpha sort
+ table.sort(t, function(x,y)
+ return t_compare (x,y,{"cat", "catprio", "group", "groupprio", "tab", "tabprio"} )
+ end)
+
+ -- Fill in the priorities
+ local fill_prio = function (t, start, stop, col)
+ local prio = t[start][col]
+ if prio == "nan" then prio = "0" end
+ while start <= stop do
+ t[start][col] = prio
+ start = start + 1
+ end
+ end
+
+
+-- Fill in the priorities
+-- Warning - UGLY code ahead.
+-- Basic rules, for each cat and group, if the prio is nan, then set it
+-- to the lowest value for that group or cat.
+ local k = 1
+ while ( k <= table.maxn(t) ) do
+ local c = k
+ while ( c <= table.maxn(t) and t[c].cat == t[k].cat ) do
+ c=c+1
+ end
+ c=c-1 -- back up one - we only want whats the same
+ fill_prio(t,k,c,"catprio")
+ -- from k,c is a mini table, do the same for groupprio
+ local g = k
+ while ( g <= c ) do
+ local h = g
+ while ( h <= c and t[h].group == t[g].group ) do
+ h=h+1
+ end
+ h=h-1 --- back up one (again)
+ fill_prio(t,g,h,"groupprio")
+ g=h+1
+ end
+ k = c + 1
+ end
+
+ -- Now priority sort
+ table.sort(t, function(x,y)
+ return t_compare (x,y,{"catprio", "cat", "groupprio", "group", "tabprio", "tab"} )
+ end)
+
+
+ -- drop the priorities - they were internal
+ for k,v in ipairs(t) do
+ v.catprio = nil
+ v.groupprio = nil
+ v.tabprio = nil
+ end
+
+ return t
+end
+
+
diff --git a/lib/service_controller.lua b/lib/service_controller.lua
new file mode 100644
index 0000000..09595a4
--- /dev/null
+++ b/lib/service_controller.lua
@@ -0,0 +1,189 @@
+local function build_status(model, name)
+ return {
+ type = "form",
+ method = "post",
+ action = cf.uri .. "/chrun",
+ value = {
+ {
+ type = "formtext",
+ name = "status",
+ label = "Status: <B>" .. name .. "</B>",
+ value = model.status(name)
+ },
+ {
+ type = "hidden",
+ name = "service",
+ value = name
+ },
+ {
+ type = "submit",
+ name = "cmd",
+ label = "Running controls",
+ value = { "Start", "Stop", "Restart" }
+ },
+ }
+ }
+end
+
+local function build_note(model, name)
+ return {
+ type = "form",
+ method = "post",
+ action = cf.uri .. "/note_set",
+ value = {
+ {
+ type = "textarea",
+ name = "note",
+ label = "Notes",
+ cols = 80,
+ rows = 8,
+ value = model.get_note(name)
+ },
+ {
+ type = "submit",
+ label = "Command",
+ value = "Save"
+ }
+ }
+ }
+end
+
+local function mkerr(str)
+ return {
+ {
+ type = "formtext",
+ class = "error",
+ value = str
+ }
+ }
+end
+
+local function mkresult(str)
+ return {
+ {
+ type = "formtext",
+ value = str
+ }
+ }
+end
+
+local function chrun(model, name, ucmd)
+ local cmd = string.lower(ucmd)
+ --TODO chect that name is in get_service_names()
+ if cmd ~= "start" and cmd ~= "stop" and cmd ~= "restart" then
+ return mkerr("unknown command")
+ end
+ return mkresult(model.initd(name, cmd))
+end
+
+local function build_log_file(model, name)
+ return {
+ type = "group",
+ label = name,
+ text = "FILE " .. model.get_log_names()[name].path,
+ value = {
+ {
+ type = "log",
+ lines = model.get_log_producer(name)
+ }
+ }
+ }
+end
+
+local function build_edit_file(model, name)
+ return {
+ type = "form",
+ method = "post",
+ action = cf.uri .. "/cfg_set",
+ value = {
+ {
+ type = "textarea",
+ name = "cfg",
+ label = name,
+ cols = 80,
+ rows = 12,
+ value = model.get_cfg(name)
+ },
+ {
+ type = "hidden",
+ name = "name",
+ value = name
+ },
+ {
+ type = "submit",
+ label = "Command",
+ value = "Save"
+ }
+ }
+ }
+
+end
+
+local function build_cfg(model)
+ local ret = {}
+ for name, whatever in pairs(model.get_cfg_names()) do
+ table.insert(ret, build_edit_file(model, name))
+ end
+ return ret
+end
+
+local function set_cfg(model)
+ for name, whatever in pairs(model.get_cfg_names()) do
+ if name == FORM.name then
+ model.set_cfg(name, FORM.cfg)
+ return
+ end
+ end
+ return mkerr("unknown config")
+end
+
+local function build_log(model)
+ local ret = {}
+ for name, whatever in pairs(model.get_log_names()) do
+ table.insert(ret, build_log_file(model, name))
+ end
+ return ret
+end
+
+local function build_service(model)
+ local ret = {}
+ for name, whatever in pairs(model.get_service_names()) do
+ table.insert(ret, build_status(model, name))
+ end
+ table.insert(ret, build_note(model))
+ return ret
+end
+
+function create_service_controller()
+
+ local me = {}
+
+ me.service = build_service
+ me.default = me.service
+ me.cfg = build_cfg
+ me.log = build_log
+
+ function me.chrun(model)
+ return chrun(model, FORM.service, FORM.cmd)
+ end
+
+ function me.note_set(model)
+ local ret = model.set_note(FORM.note)
+ if ret then
+ return ret
+ end
+ return me.service(model)
+ end
+
+
+ function me.cfg_set(model)
+ set_cfg(model)
+ return me.cfg(model)
+ end
+
+ return me
+
+end
+
+-- /* vim: set filetype=lua shiftwidth=4: */
+
diff --git a/lib/service_model.lua b/lib/service_model.lua
new file mode 100644
index 0000000..c619d61
--- /dev/null
+++ b/lib/service_model.lua
@@ -0,0 +1,115 @@
+require("fs")
+
+function create_service_model(cfglist, loglist, servlist, notepath)
+
+ local me = {}
+
+ local function get_any_pid(pname)
+ for e in lfs.dir("/proc") do
+ if e == string.match(e, "^%d*$") then
+ for line in io.lines("/proc/" .. e .. "/status") do
+ tag, val = string.match(line, "^([^:]*):%s*(%S*)$");
+ if tag == "Name" then
+ if val == pname then return e end
+ break
+ end
+ end
+ end
+ end
+ end
+
+ local function get_cfg_path(name)
+ if not cfglist or not cfglist[name] then
+ return
+ end
+ return cfglist[name].path
+ end
+
+ local function get_log_path(name)
+ if not loglist or not loglist[name] then
+ return
+ end
+ return loglist[name].path
+ end
+
+ --[[ Public Functions go here ]]--
+
+ function me.status(name)
+ if not servlist or not servlist[name] then
+ return "unknown service"
+ end
+ local cmdname = servlist[name].cmdname
+ if get_any_pid(cmdname) == nil then
+ return "seems to be stopped"
+ else
+ return "seems to be running"
+ end
+ end
+
+ function me.initd(name, action)
+ if not servlist or not servlist[name] then
+ return "unknown service"
+ end
+ local p = io.popen("/etc/init.d/"
+ .. servlist[name].initdname .. " " .. action, "r")
+ local ret = p:read("*a")
+ p:close()
+ return ret
+ end
+
+ function me.get_note()
+ if not notepath or not fs.is_file(notepath) then return "" end
+ return fs.read_file(notepath)
+ end
+
+ function me.set_note(value)
+ if notepath == nil then return end
+ fs.write_file(notepath, value)
+ end
+
+ function me.get_cfg_names()
+ return cfglist
+ end
+
+ function me.get_log_names()
+ return loglist
+ end
+
+ function me.get_service_names()
+ return servlist
+ end
+
+ function me.get_cfg(name)
+ local path = get_cfg_path(name)
+ if not path or not fs.is_file(path) then
+ return ""
+ end
+ return fs.read_file(path)
+ end
+
+ function me.set_cfg(name, value)
+ local path = get_cfg_path(name)
+ if path then
+ fs.write_file(path, value)
+ end
+ end
+
+-- local function wrap_single(x)
+-- return function(state, prev)
+-- if not prev then
+-- return x
+-- end
+-- end
+-- end
+
+ function me.get_log_producer(name)
+ local path = get_log_path(name)
+ if not path or not fs.is_file(path) then
+ return "cannot access " .. path
+ end
+ return io.lines(path)
+ end
+
+ return me
+
+end
diff --git a/lib/session.lua b/lib/session.lua
new file mode 100644
index 0000000..f23f5a5
--- /dev/null
+++ b/lib/session.lua
@@ -0,0 +1,143 @@
+-- Session handling routines - written for acf
+-- Copyright (C) 2007 N. Angelacos - GPL2 License
+
+module (..., package.seeall)
+
+local b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
+
+-- Return a sessionid of at least size bits length
+random_hash = function (size)
+ local file = io.open("/dev/urandom")
+ local str = ""
+ if file == nil then return nil end
+ while (size > 0 ) do
+ local offset = (string.byte(file:read(1)) % 64) + 1
+ str = str .. string.sub (b64, offset, offset)
+ size = size - 6
+ end
+ return str
+end
+
+hash_ip_addr = function (string)
+ local str = ""
+ for i in string.gmatch(string, "%d+") do
+ str = str .. string.format("%02x", i )
+ end
+ return str
+end
+
+ip_addr_from_hash = function (string)
+ local str = ""
+ for i in string.gmatch(string, "..") do
+ str = str .. string.format("%d", "0x" .. i) .. "."
+ end
+ return string.sub(str, 1, string.len(str)-1)
+end
+
+
+--[[
+ These functions serialize a table, including nested tables.
+ The code based on code in PiL 2nd edition p113
+]]--
+local function basicSerialize (o)
+ if type(o) == "number" then
+ return tostring(o)
+ else
+ return string.format("%q", o)
+ end
+end
+
+
+function serialize (name, value, saved )
+ local str = str or ""
+ saved = saved or {}
+ str = str .. name .. " = "
+ if type(value) == "number" or type(value) == "string" then
+ str = str .. basicSerialize (value) .. "\n"
+ elseif type(value) == "table" then
+ if saved[value] then
+ str = str .. saved[value] .. "\n"
+ else
+ saved[value] = name
+ str = str .. "{}\n"
+ for k,v in pairs(value) do
+ local fieldname = string.format("%s[%s]", name, basicSerialize(k))
+ str = str .. serialize (fieldname, v, saved)
+ end
+ end
+ elseif type(value) == "boolean" then
+ str = str .. tostring(value) .. "\n"
+ else
+ str = str .. "nil\n" -- cannot save other types, so skip them
+ end
+ return str
+end
+
+save_session = function( sessionpath, session, sessiontable)
+ local file = io.open(sessionpath .. "/" .. session , "w")
+ if file then
+ file:write ( "-- This is an ACF session table.\nlocal timestamp=" .. os.time() )
+ file:write ( "\nlocal " )
+ file:write ( serialize("s", sessiontable) )
+ file:write ( "return timestamp, s\n")
+ file:close()
+ return true
+ else
+ return false
+ end
+end
+
+
+--- FIXME: This is really a generic "test if file exists" thing.
+
+-- Tests if a session is valid
+-- Returns true if valid, false if not
+session_is_valid = function (session)
+ local file = io.open(session)
+ if file then
+ file:close()
+ return true
+ else
+ return false
+ end
+end
+
+-- Loads a session
+-- Returns a timestamp (when the session data was saved) and the session table.
+load_session = function ( sessionpath, session )
+ -- session can only have b64 characters in it
+ session = string.gsub ( session, "[^" .. b64 .. "]", "")
+ if #session == 0 then
+ return nil, {}
+ end
+ session = sessionpath .. "/" .. session
+ if (session_is_valid(session)) then
+ local file = io.open(session)
+ return dofile(session)
+ else
+ return nil, {}
+ end
+end
+
+-- unlinks a session
+invalidate_session = function (sessionpath, session)
+ if type(session) ~= "string" then return nil end
+ local s = string.gsub (session, "[^" .. b64 .. "]", "")
+ if s ~= session then
+ return nil
+ end
+ session = sessionpath .. "/" .. s
+ os.remove (session)
+ return nil
+end
+
+
+expire_old_sessions = function ( sessiondir )
+ file = io.popen("ls " .. sessiondir )
+ x = file:read("*a")
+ file:close()
+ for a in string.gmatch(x, "[^%c]+") do
+ timestamp, foo = load_session ( sessiondir .. "/" .. a )
+ print ( a .. " is " .. os.time() - timestamp .. " seconds old")
+ end
+end
diff --git a/lib/split.lua b/lib/split.lua
new file mode 100644
index 0000000..f3e2e95
--- /dev/null
+++ b/lib/split.lua
@@ -0,0 +1,31 @@
+--[[
+ module for splitting a string to an array
+]]--
+
+module (..., package.seeall)
+
+-- This code comes from http://lua-users.org/wiki/SplitJoin
+
+-- Split text into a list consisting of the strings in text,
+-- separated by strings matching delimiter (which may be a pattern).
+-- example: strsplit(",%s*", "Anna, Bob, Charlie,Dolores")
+return function (delimiter, text)
+ local list = {}
+ local pos = 1
+ -- this would result in endless loops
+ if string.find("", delimiter, 1) then
+ error("delimiter matches empty string!")
+ end
+ while 1 do
+ local first, last = string.find(text, delimiter, pos)
+ if first then -- found?
+ table.insert(list, string.sub(text, pos, first-1))
+ pos = last+1
+ else
+ table.insert(list, string.sub(text, pos))
+ break
+ end
+ end
+ return list
+end
+
diff --git a/lib/validator.lua b/lib/validator.lua
new file mode 100755
index 0000000..951d716
--- /dev/null
+++ b/lib/validator.lua
@@ -0,0 +1,156 @@
+--------------------------------------------------
+-- Validation Functions for Alpine Linux' Webconf
+--------------------------------------------------
+
+-- setup the 'language' table
+lang = {}
+lang.English = 1
+lang.German = 2
+lang.French = 3
+lang.Current = lang.English
+
+-- setup the 'validator' tables
+validator = {}
+validator.msg = {}
+validator.msg.err = {}
+
+-- setup error messages
+validator.msg.err.Success = {}
+validator.msg.err.Success[lang.English] = "Ok."
+validator.msg.err.Success[lang.German] = "Ok."
+validator.msg.err.InvalidChars = {}
+validator.msg.err.InvalidChars[lang.English] = "Invalid characters!"
+validator.msg.err.InvalidChars[lang.German] = "Ungültige Zeichen!"
+validator.msg.err.InvalidLength = {}
+validator.msg.err.InvalidLength[lang.English] = "Invalid length!"
+validator.msg.err.InvalidLength[lang.German] = "Ungültige Länge!"
+validator.msg.err.InvalidFormat = {}
+validator.msg.err.InvalidFormat[lang.English] = "Invalid format!"
+validator.msg.err.InvalidFormat[lang.German] = "Ungültiges Format!"
+validator.msg.err.InvalidValue = {}
+validator.msg.err.InvalidValue[lang.English] = "Invalid Value!"
+validator.msg.err.InvalidValue[lang.German] = "Ungültiger Wert!"
+validator.msg.err.OutOfRange = {}
+validator.msg.err.OutOfRange[lang.English] = "Value out of range!"
+validator.msg.err.OutOfRange[lang.German] = "Wert ausserhalb des Bereichs!"
+
+--
+-- This function validates an ipv4 address.
+-- On success it returns 1 otherwise a negative value
+--
+function validator.is_ipv4(ipv4)
+ local retval = false;
+ local nums = { "", "", "", ""};
+ local iplen = string.len(ipv4);
+
+ -- check the ipv4's length
+ if (iplen < 7 or iplen > 15) then
+ return false, validator.msg.err.InvalidLength[lang.Current]
+ end
+
+ -- NC: Split the string into an array. separate with '.' (dots)
+ -- ^ beginning of string
+ -- () capture
+ -- \. litteral '.' The \ neutralizes the . character class.
+ -- %d+ one or more digits
+ -- $ end of string
+ nums = { ipv4:match ("^(%d+)\.(%d+)\.(%d+)\.(%d+)$" ) }
+
+ -- check if all nums are filled
+ if ( nums[1] == nil or
+ nums[2] == nil or
+ nums[3] == nil or
+ nums[4] == nil) then
+ -- we have an empty number
+ return false, validator.msg.err.InvalidFormat[lang.Current]
+ end
+
+ -- too big?
+ if (tonumber(nums[1]) > 255 or
+ tonumber(nums[2]) > 255 or
+ tonumber(nums[3]) > 255 or
+ tonumber(nums[4]) > 255) then
+ -- at least one number is too big
+ return false, validator.msg.err.InvalidValue[lang.Current]
+ end
+
+ return true, validator.msg.err.Success[lang.Current]
+end
+
+function validator.is_mac(mac)
+
+ local tmpmac = string.upper(mac)
+
+ if (string.len(tmpmac) ~= 17) then
+ return false, validator.msg.err.InvalidLength[lang.Current]
+ end
+
+ -- check for valid characters
+ local step = 1;
+ while (step <= 17) do
+ if (string.sub(tmpmac, step, step) ~= ":") and
+ (string.sub(tmpmac, step, step) < "0" or string.sub(tmpmac, step, step) > "9") and
+ (string.sub(tmpmac, step, step) < "A" or string.sub(tmpmac, step, step) > "F") then
+ -- we have found an invalid character!
+ return false, validator.msg.err.InvalidChars[lang.Current]
+ end
+ step = step + 1;
+ end
+
+ -- check for valid colon positions
+ if (string.sub(tmpmac, 3, 3) ~= ":" or
+ string.sub(tmpmac, 6, 6) ~= ":" or
+ string.sub(tmpmac, 9, 9) ~= ":" or
+ string.sub(tmpmac, 12, 12) ~= ":" or
+ string.sub(tmpmac, 15, 15) ~= ":") then
+ return false, validator.msg.err.InvalidFormat[lang.Current]
+ end
+
+ -- check for valid non colon positions
+ step = 1;
+ while (step <= 17) do
+ if ((string.sub(tmpmac, step, step) == ":") and
+ ((step ~= 3) and (step ~= 6) and (step ~= 9) and (step ~= 12) and
+ (step ~= 15))) then
+ return false, validator.msg.err.InvalidValue[lang.Current]
+ end
+ step = step + 1;
+ end
+
+ return true, validator.msg.err.Success[lang.Current]
+end
+
+--
+-- This function checks if the given input
+-- consists of number-chars between 0..9 only
+-- and eventually a leading '-'
+--
+function validator.is_integer(numstr)
+ -- ^ beginning of string
+ -- -? one or zero ot the char '-'
+ -- %d+ one or more digits
+ -- $ end of string
+ return string.find(numstr, "^-?%d+$") ~= nil
+end
+
+
+--
+-- This function checks if the given input
+-- consists of number-chars between 0..9 only
+-- and if it is within a given range.
+--
+function validator.is_integer_in_range(numstr, min, max)
+ return validator.is_integer(numstr)
+ and numstr >= min
+ and numstr <= max
+
+end
+
+--
+-- This function checks if the given number is an integer
+-- and wheter it is between 1 .. 65535
+--
+function validator.is_port(numstr)
+ return validator.is_integer_in_range(numstr, 1, 65535)
+end
+
diff --git a/lib/web_elements.lua b/lib/web_elements.lua
new file mode 100644
index 0000000..e676205
--- /dev/null
+++ b/lib/web_elements.lua
@@ -0,0 +1,156 @@
+--[[
+ middle level functions for the webconf view templates. Part of
+ acf
+
+ Copyright (C) 2006 N. Angelacos Licensed under terms of GPL2
+]]--
+
+module ( ..., package.seeall )
+
+require ("html")
+
+-- This is the main function that walks a table formatted for a template
+-- (This is the magic in generic)
+function render_table ( element, level)
+ local level = level or 1
+
+ if (type(element) ~= "table" ) then
+ return nil
+ end
+
+ for k,v in pairs (element) do
+ if ( v.type ~= nil ) then
+ if ( v.type == "group" ) then
+ print ( html.entity ( ( "h" .. tostring(level) ), v.label, v.class, v.id ) )
+ print ( html.entity ( "p" , v.text, v.class ) )
+ render_table ( v.value, level + 1 )
+ elseif ( v.type == "label" ) then
+ print ( html.entity ( "h" .. level , v.value, v.class, v.id ) )
+ if ( v.text ~= nil ) then
+ print ( html.entity ( "p", v.text, v.class ))
+ end
+ elseif ( v.type == "html" ) then
+ print (v.value)
+ elseif ( v.type == "log" ) then
+ print("<pre>")
+ if type(v.lines) == "function" then
+ for line in v.lines do
+ print(line)
+ end
+ elseif v.lines then
+ print(v.lines)
+ end
+ print("</pre>")
+ elseif ( v.type == "link" ) then
+ print (html.link ( v ) )
+ elseif ( v.type == "form" ) then
+ print ( html.form.start ( v ) )
+ print ("<dl>")
+ render_table ( v.value, level + 1 )
+ print ("</dl>")
+ print ( html.form.stop () )
+ elseif type(html.form[v.type]) ~= "nil" then
+ if v.type == "hidden" then
+ v.noitem = true
+ end
+ if not v.noitem then
+ print ( string.format ("<dt>%s</dt><dd>", ( v.label or "" )))
+ end
+ print ( html.form[v.type] ( v ) )
+ if not v.noitem then
+ print ("</dd>")
+ end
+ end
+ end
+ end
+end
+
+
+-- This function prints the main menu, with the given prefix/controller "selected"
+-- returns the group, category, subcat that is selected
+function render_mainmenu ( menu, prefix, controller, action )
+ -- prefix+controller defines which menu is "selected"
+ local megroup = nil
+ local mecat = nil
+ local mesubcat = nil
+ local liston = nil
+ local group = ""
+ local cat = ""
+
+ -- find the current group/cat/subcat
+ for i=1,table.maxn(menu) do
+ if (menu[i].prefix == prefix) and ( menu[i].controller == controller ) then
+ megroup = menu[i].group
+ mecat = menu[i].cat
+ if ( menu[i].action == action ) then
+ mesubcat = menu[i].subcat
+ elseif ( menu[i].action == "*" ) and ( mesubcat == nil ) then
+ mesubcat = menu[i].subcat
+ end
+ end
+ end
+
+ -- render the mainmenu
+ local thisgroup = ""
+ local thiscat = ""
+ for i=1,table.maxn(menu),1 do
+ if menu[i].group ~= thisgroup then
+ thisgroup = menu[i].group
+ if ( liston ) then io.write ("</ul>") end
+ print ( html.entity ( "h3", menu[i].group ) )
+ io.write("<ul>")
+ liston = true
+ thicat = nil
+ end
+ if menu[i].cat ~= thiscat then
+ thiscat = menu[i].cat
+ if (thiscat == mecat ) then
+ print ( html.entity ("li", html.html_escape(thiscat), nil, "selected"))
+ else
+ print (html.link ( { value= ENV.SCRIPT_NAME .. menu[i].uri ,
+ label = html.entity ("li", html.html_escape(thiscat)) } ) )
+ end
+ end
+ end
+ io.write ("</ul>")
+ return megroup, mecat, mesubcat
+end
+
+
+
+-- This function prints the tabs for the submenu, with the given submenu "selected"
+function render_submenu ( menu, group, cat, subcat )
+ cat = cat or ""
+ group = group or ""
+ local this_subcat = nil
+ local foo = group .. " > " .. cat
+ if (foo ~= " > " ) then
+ print ( html.entity ( "h2", html.html_escape( group .. " > " .. cat ) ))
+ end
+
+
+ -- print (string.format ("%s - %s - %s", group, cat , (subcat or "")))
+
+ io.write ("<ul>")
+ for i=1, table.maxn(menu),1 do
+ if ( group == menu[i].group ) and ( cat == menu[i].cat ) then
+ -- If a subcat was not selected, make the first one the default
+ if ( subcat == nil ) then
+ subcat = menu[i].subcat
+ end
+
+ if ( menu[i].subcat ~= this_subcat ) then
+ this_subcat = menu[i].subcat
+ if ( menu[i].subcat == subcat ) then
+ print ( html.entity ("li", menu[i].subcat, nil, "selected"))
+ else
+ io.write ("<li>")
+ io.write ( html.link ( { value= ENV.SCRIPT_NAME .. menu[i].uri ,
+ label =menu[i].subcat } ) )
+ print ("</li>")
+ end
+ end
+ end
+ end
+ io.write ("</ul>")
+end