Friday, October 23, 2015

LUCI LUA MVC

Recently I had the opportunity to work on OpenWRT for a Network Security Product. The hardest part for getting the product finished and delivered was customizing the UI.

The default UI of OpenWRT uses LUCI Model-View-Controller Framework. In a traditionally know language like php, Java, javascript, working on MVC is always a pleasure for UI developers. I am by choice a Kernel programmer, and even I would, for once in a while case, touch upon and write some UI components. However, working on LUA based LUCI was a night mare, not only for a non professional UI guy like me, but even for veterans in UI in my team.

The primary reasons why this was the case with LUCI-LUA.
1. No documentation. ( I mean even the kernel has better documentation than LUCI-LUA)
2. The code is not written in a generally accepted and used language (Its written in Lua Language).
3. No one else has written a memoir about there tryst with LUCI-LUA.

So I decided that since recently I just finished adding a dozen custom changes to it, I should share them with readers who might stumble upon it.

The first feature which is not supported by LUCI-LUA is that it is mostly hard-coded with 1 admin user "root". Inherently it does not have multi-user support. But there are hacks lying around to make LUCI-LUA pseudo multi-user.


The reason it is pseudo multi-user is because, even though a new user "Admin" logs into the UI, the underlying user in the linux system is still "root", because the http server which serves LUCI pages is itself running as root.

Suppose you need to add a custom page for a new feature. As an example we add a new Tab under System tab which is to add External Admins configured in a RADIUS Server.

In that case take an existing Template ( like we all do while developing drivers....look into an existing similar code). Add your entry into the System.lua file and then a blank page with necessary CSS and theme automatically pops up with your Menu entry.

Then add your own lua script which does handling of all the logic and UI handling.

Code Snippets:

FIle: system.lua
entry({"admin", "system", "externaladmin"}, cbi("admin_system/externaladmin"), _("External Admin"), 97)

File: externaladmin.lua.

adminform = SimpleForm("userform1", translate("External Admin"))
adminform.reset  = false
username = adminform:field(Value, "uname",translate("Username"))
serverip = adminform:field(Value, "serverip",translate("Server IP"))
key = adminform:field(Value, "key",translate("Key"), translate("Table1: External Admins maintained
in External Radius Server. Table2: Radius Server with Server IPs"))

function adminform.handle(self, state, data)

if state == FORM_VALID then

local file = io.open("/tmp/myuser.log","w+")
local usernametxt = data.uname
local serveriptxt = data.serverip
local keytxt = data.key
local serverfile

if serveriptxt and keytxt and #serveriptxt > 0 and #keytxt > 0 then
command = "sh /root/radius_adt.sh "..serveriptxt.." "..keytxt
os.execute(command)
luci.http.redirect(luci.dispatcher.build_url("admin/system/externaladmin"))
end

if usernametxt and #usernametxt > 0 then
command = "sh /root/sysauth_adt.sh add external "..usernametxt.." NULL"
--os.execute(command)
output = luci.util.exec(command)
if output ~= "" then
adminform.errmessage=output
else
luci.http.redirect(luci.dispatcher.build_url("admin/system/externaladmin"))
end
end

file:close()

-- luci.http.redirect(luci.dispatcher.build_url("admin/system/externaladmin"))

end

return true
end

--[[ This section adds table to display External Admins ]]--

tbluser = adminform:section(Table, userdata)

tblcoluser = tbluser:option(DummyValue, "coluser", translate("User Name"))
tblcoldel = tbluser:option(Button, "deluser", translate("Delete"))

tblcoldel.render = function(self, section, scope)
if userdata[section].enabled then
self.title = translate("Delete")
self.inputstyle = "delete"
end
Button.render(self, section, scope)
end

tblcoldel.write = function(self, section)
local userdel = userdata[section].coluser
command = "sh /root/sysauth_adt.sh remove external "..userdel.." NULL"
--command = "deluser "..userdel
os.execute(command)
luci.http.redirect(luci.dispatcher.build_url("admin/system/externaladmin"))
end

Rest of the template remains same. Much of my LUCI LUA experience has been constantly looking at existing code and putting debug prints to understand the flow.

If you want to add a Start up script UI element which allows you to DO a ENABLE/START/STOP/RESTART of your script, then just write a init script.

#!/bin/sh /etc/rc.common
# script to Run DHCP snoop module using previous state
# Copyright (C) 2015 Nevis Networks
# Author: Gadre Nayan Anand Version: 1.0
# This is a init script to enable, start, stop, restart DHCP snoop Feature.
# For More details on commands involved read User Manual.

START=58
STOP=58

FILE1=/etc/config/dhcp_sis
FILE2=/etc/config/trusted_interfaces

start()
{
        local p_m
        local u_b
        while IFS="=" read -r key value;
        do
                case "$key" in
                        "preventive_mode") p_m=$value ;;
                        "use_bridge") u_b=$value;;
                esac
        done < "$FILE1"
        echo preventive_mode = $p_m use_bridge = $u_b

        echo "starting DHCP SNOOP IP SPOOF"
        /usr/sbin/insmod  /root/dhcp_snoop_IP_spoof.ko preventive_mode=$p_m use_bridge=$u_b

while read -r key;
do
echo "a-$key" > /sys/kernel/dhcp/trusted_interfaces
done < "$FILE2"
}

stop()
{
        echo "stopping DHCP SNOOP IP SPOOF"
        /usr/sbin/rmmod dhcp_snoop_IP_spoof
}



And LUCI framework  is really pathetically rigid, especially with the GUI perspective. CSS and other aspects are very hard to change....atleast this is what I felt.

No comments:

Post a Comment