Merge pull request #1709 from dibdot/get_interface-fix
authorJo-Philipp Wich <jo@mein.io>
Thu, 5 Apr 2018 07:49:49 +0000 (09:49 +0200)
committerGitHub <noreply@github.com>
Thu, 5 Apr 2018 07:49:49 +0000 (09:49 +0200)
luci-base/network.lua: fix get_interface function

38 files changed:
applications/luci-app-adblock/luasrc/controller/adblock.lua
applications/luci-app-commands/luasrc/view/commands.htm
applications/luci-app-cshark/luasrc/controller/cshark.lua
applications/luci-app-ddns/luasrc/controller/ddns.lua
applications/luci-app-freifunk-diagnostics/luasrc/controller/freifunk/diag.lua
applications/luci-app-freifunk-diagnostics/luasrc/view/freifunk/diagnostics.htm
applications/luci-app-mwan3/luasrc/controller/mwan3.lua
applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua
applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm
applications/luci-app-mwan3/luasrc/view/mwan/status_detail.htm
applications/luci-app-mwan3/luasrc/view/mwan/status_diagnostics.htm
applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm
applications/luci-app-mwan3/luasrc/view/mwan/status_troubleshooting.htm
applications/luci-app-olsr/luasrc/controller/olsr.lua
applications/luci-app-openvpn/luasrc/model/cbi/openvpn.lua
applications/luci-app-splash/luasrc/controller/splash/splash.lua
applications/luci-app-splash/root/usr/sbin/luci-splash
applications/luci-app-statistics/luasrc/statistics/rrdtool.lua
applications/luci-app-tinyproxy/luasrc/view/tinyproxy_status.htm
applications/luci-app-upnp/luasrc/controller/upnp.lua
applications/luci-app-vnstat/luasrc/view/vnstat.htm
applications/luci-app-wol/luasrc/model/cbi/wol.lua
modules/luci-base/htdocs/luci-static/resources/cbi.js
modules/luci-base/luasrc/cbi/datatypes.lua
modules/luci-base/luasrc/dispatcher.lua
modules/luci-base/luasrc/model/ipkg.lua
modules/luci-base/luasrc/model/uci.lua
modules/luci-base/luasrc/sys.lua
modules/luci-base/luasrc/tools/status.lua
modules/luci-base/luasrc/util.lua
modules/luci-base/luasrc/util.luadoc
modules/luci-base/luasrc/view/sysauth.htm
modules/luci-base/po/zh-cn/base.po
modules/luci-mod-admin-full/luasrc/controller/admin/network.lua
modules/luci-mod-admin-full/luasrc/controller/admin/status.lua
modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua
modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/vlan.lua
modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua

index 700f187..763c0b4 100644 (file)
@@ -36,8 +36,8 @@ end
 function queryData(domain)
        if domain then
                luci.http.prepare_content("text/plain")
-               local cmd = "/etc/init.d/adblock query %q 2>&1"
-               local util = io.popen(cmd % domain)
+               local cmd = "/etc/init.d/adblock query %s 2>&1"
+               local util = io.popen(cmd % util.shellquote(domain))
                if util then
                        while true do
                                local line = util:read("*l")
index f094e18..3b36155 100644 (file)
        uci:foreach("luci", "command", function(s) commands[#commands+1] = s end)
 %>
 
-<form method="get" action="<%=pcdata(luci.http.getenv("REQUEST_URI"))%>">
+<form method="get" action="<%=pcdata(FULL_REQUEST_URI)%>">
        <div class="cbi-map">
                <h2 name="content"><%:Custom Commands%></h2>
 
index 4d9bbba..43410a0 100644 (file)
@@ -53,7 +53,13 @@ function cshark_iface_dump_start(ifname, value, flag, filter)
 
        luci.http.prepare_content("text/plain")
 
-       local res = os.execute("(/sbin/cshark -i " .. ifname .. " -" .. flag .. " " .. value .. " -p /tmp/cshark-luci.pid " .. filter .. " > /tmp/cshark-luci.out 2>&1) &")
+       local res = os.execute("(/sbin/cshark -i %s -%s %s -p /tmp/cshark-luci.pid %s > /tmp/cshark-luci.out 2>&1) &" %{
+               luci.util.shellquote(ifname),
+               luci.util.shellquote(flag),
+               luci.util.shellquote(value),
+               luci.util.shellquote(filter)
+       })
+
        luci.http.write(tostring(res))
 end
 
index 3d31e4e..5f4a511 100755 (executable)
@@ -301,7 +301,7 @@ function startstop(section, enabled)
        uci:unload("ddns")
 
        -- start ddns-updater for section
-       local command = luci_helper .. [[ -S ]] .. section .. [[ -- start]]
+       local command = "%s -S %s -- start" %{ luci_helper, UTIL.shellquote(section) }
        os.execute(command)
        NX.nanosleep(3) -- 3 seconds "show time"
 
index 7bb4761..92b3afc 100644 (file)
@@ -33,7 +33,7 @@ function diag_command(cmd, addr)
        if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then
                luci.http.prepare_content("text/plain")
 
-               local util = io.popen(cmd % addr)
+               local util = io.popen(cmd % luci.util.shellquote(addr))
                if util then
                        while true do
                                local ln = util:read("*l")
@@ -52,21 +52,21 @@ function diag_command(cmd, addr)
 end
 
 function diag_ping(addr)
-       diag_command("ping -c 5 -W 1 %q 2>&1", addr)
+       diag_command("ping -c 5 -W 1 %s 2>&1", addr)
 end
 
 function diag_traceroute(addr)
-       diag_command("traceroute -q 1 -w 1 -n %q 2>&1", addr)
+       diag_command("traceroute -q 1 -w 1 -n %s 2>&1", addr)
 end
 
 function diag_nslookup(addr)
-       diag_command("nslookup %q 2>&1", addr)
+       diag_command("nslookup %s 2>&1", addr)
 end
 
 function diag_ping6(addr)
-       diag_command("ping6 -c 5 %q 2>&1", addr)
+       diag_command("ping6 -c 5 %s 2>&1", addr)
 end
 
 function diag_traceroute6(addr)
-       diag_command("traceroute6 -q 1 -w 2 -n %q 2>&1", addr)
+       diag_command("traceroute6 -q 1 -w 2 -n %s 2>&1", addr)
 end
index fe205d0..eac1ecd 100644 (file)
@@ -53,7 +53,7 @@ local has_traceroute6 = fs.access("/usr/bin/traceroute6")
        }
 //]]></script>
 
-<form method="post" action="<%=pcdata(luci.http.getenv("REQUEST_URI"))%>">
+<form method="post" action="<%=pcdata(FULL_REQUEST_URI)%>">
        <div class="cbi-map">
                <h2 name="content"><%:Diagnostics%></h2>
 
index 64ee9f5..27dc984 100644 (file)
@@ -98,7 +98,7 @@ function diagnosticsData(interface, task)
 
        function diag_command(cmd, addr)
                if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then
-                       local util = io.popen(cmd % addr)
+                       local util = io.popen(cmd % ut.shellquote(addr))
                        if util then
                                while true do
                                        local ln = util:read("*l")
@@ -138,7 +138,7 @@ function diagnosticsData(interface, task)
                if task == "ping_gateway" then
                        local gateway = get_gateway(interface)
                        if gateway ~= nil then
-                               diag_command("ping -c 5 -W 1 %q 2>&1", gateway)
+                               diag_command("ping -c 5 -W 1 %s 2>&1", gateway)
                        else
                                luci.http.prepare_content("text/plain")
                                luci.http.write(string.format("No gateway for interface %s found.", interface))
@@ -147,7 +147,7 @@ function diagnosticsData(interface, task)
                        local trackips = uci:get("mwan3", interface, "track_ip")
                        if #trackips > 0 then
                                for i in pairs(trackips) do
-                                       diag_command("ping -c 5 -W 1 %q 2>&1", trackips[i])
+                                       diag_command("ping -c 5 -W 1 %s 2>&1", trackips[i])
                                end
                        else
                                luci.http.write(string.format("No tracking Hosts for interface %s defined.", interface))
@@ -185,10 +185,10 @@ function diagnosticsData(interface, task)
                                luci.http.write(string.format("Routing table %s for interface %s not found", number, interface))
                        end
                elseif task == "hotplug_ifup" then
-                       os.execute(string.format("/usr/sbin/mwan3 ifup %s", interface))
+                       os.execute(string.format("/usr/sbin/mwan3 ifup %s", ut.shellquote(interface)))
                        luci.http.write(string.format("Hotplug ifup sent to interface %s", interface))
                elseif task == "hotplug_ifdown" then
-                       os.execute(string.format("/usr/sbin/mwan3 ifdown %s", interface))
+                       os.execute(string.format("/usr/sbin/mwan3 ifdown %s", ut.shellquote(interface)))
                        luci.http.write(string.format("Hotplug ifdown sent to interface %s", interface))
                else
                        luci.http.write("Unknown task")
index 14bf138..556a4f7 100644 (file)
@@ -5,15 +5,15 @@
 dsp = require "luci.dispatcher"
 
 
-function interfaceWarnings(overview, count)
+function interfaceWarnings(overview, count, iface_max)
        local warnings = ""
-       if count <= 250 then
+       if count <= iface_max then
                warnings = string.format("<strong>%s</strong><br />",
-                       translatef("There are currently %d of 250 supported interfaces configured", count)
+                       translatef("There are currently %d of %d supported interfaces configured", count, iface_max)
                        )
        else
                warnings = string.format("<strong>%s</strong><br />",
-                       translatef("WARNING: %d interfaces are configured exceeding the maximum of 250!", count)
+                       translatef("WARNING: %d interfaces are configured exceeding the maximum of %d!", count, iface_max)
                        )
        end
 
@@ -103,7 +103,34 @@ function configCheck()
                        end
                end
        )
-       return overview, count
+
+       -- calculate iface_max usage from firewall mmx_mask
+       function bit(p)
+               return 2 ^ (p - 1)
+       end
+       function hasbit(x, p)
+               return x % (p + p) >= p
+       end
+       function setbit(x, p)
+               return hasbit(x, p) and x or x + p
+       end
+
+       local uci = require("uci").cursor(nil, "/var/state")
+       local mmx_mask = uci:get("mwan3", "globals", "mmx_mask") or "0x3F00"
+       local number = tonumber(mmx_mask, 16)
+       local bits = 0
+       local iface_max = 0
+       for i=1,16 do
+               if hasbit(number, bit(i)) then
+                       bits = bits + 1
+                       iface_max = setbit( iface_max, bit(bits))
+               end
+       end
+
+       -- subtract blackhole, unreachable and default table from iface_max
+       iface_max = iface_max - 3
+
+       return overview, count, iface_max
 end
 
 m5 = Map("mwan3", translate("MWAN - Interfaces"),
index 4ec0edf..49d120c 100644 (file)
@@ -19,7 +19,7 @@ XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "interface_
                                        switch (status.interfaces[iface].status)
                                        {
                                                case 'online':
-                                                       state = '<%:Online (tracking active)%>';
+                                                       state = '<%:Online%>';
                                                        css = 'success';
                                                        break;
                                                case 'offline':
@@ -69,6 +69,7 @@ XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "interface_
 <fieldset id="interface_field" class="cbi-section">
        <legend><%:MWAN Interfaces%></legend>
        <div id="mwan_status_text">
-               <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /><%:Collecting data...%>
+               <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" />
+               <%:Collecting data...%>
        </div>
 </fieldset>
index 70eac72..bcc23be 100644 (file)
@@ -18,9 +18,7 @@
        XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "detailed_status")%>', null,
                function(x)
                {
-                       var legend = document.getElementById('diag-rc-legend');
                        var output = document.getElementById('diag-rc-output');
-                       legend.style.display = 'none';
                        output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
                }
        );
@@ -32,9 +30,9 @@
        <div><strong><%:INFO: MWAN not running%></strong></div>
        <%end%>
        <fieldset class="cbi-section">
-               <legend id="diag-rc-legend"><%:Collecting data...%></legend>
                <span id="diag-rc-output">
                        <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />
+                       <%:Collecting data...%>
                </span>
        </fieldset>
 </div>
index f1c5d8f..22f4734 100644 (file)
@@ -31,7 +31,6 @@
 
        function update_status(iface, task)
        {
-               var legend = document.getElementById('diag-rc-legend');
                var output = document.getElementById('diag-rc-output');
 
                output.innerHTML =
@@ -45,7 +44,6 @@
                stxhr.post('<%=url('admin/status/mwan')%>/diagnostics_display' + '/' + iface + '/' + task, { token: '<%=token%>' },
                        function(x)
                        {
-                               legend.style.display = 'none';
                                output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
                        }
                );
@@ -86,7 +84,6 @@
                </fieldset>
        </div>
        <fieldset class="cbi-section" style="display:none">
-               <legend id="diag-rc-legend"><%:Collecting data...%></legend>
                <span id="diag-rc-output"></span>
        </fieldset>
 </form>
index cb47696..4518bd6 100644 (file)
@@ -15,6 +15,7 @@
 
 <script type="text/javascript" src="<%=resource%>/cbi.js"></script>
 
-<%+mwan/overview_status_interface%>
-
+<div class="cbi-map">
+       <%+mwan/overview_status_interface%>
+</div>
 <%+footer%>
index 77d0092..f60e0da 100644 (file)
@@ -18,9 +18,7 @@
        XHR.poll(15, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "troubleshooting_display")%>', null,
                function(x)
                {
-                       var legend = document.getElementById('diag-rc-legend');
                        var output = document.getElementById('diag-rc-output');
-                       legend.style.display = 'none';
                        output.innerHTML = String.format('<pre>%h</pre>', x.responseText);
                }
        );
@@ -32,9 +30,9 @@
        <div><strong><%:INFO: MWAN not running%></strong></div>
        <%end%>
        <fieldset class="cbi-section">
-               <legend id="diag-rc-legend"><%:Collecting data...%></legend>
                <span id="diag-rc-output">
                        <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />
+                       <%:Collecting data...%>
                </span>
        </fieldset>
 </div>
index 229f3d6..c5fb2b2 100644 (file)
@@ -84,11 +84,11 @@ function action_json()
        local jsonreq4
        local jsonreq6
 
-       local v4_port = uci:get("olsrd", "olsrd_jsoninfo", "port") or 9090
-       local v6_port = uci:get("olsrd6", "olsrd_jsoninfo", "port") or 9090
+       local v4_port = tonumber(uci:get("olsrd", "olsrd_jsoninfo", "port") or "") or 9090
+       local v6_port = tonumber(uci:get("olsrd6", "olsrd_jsoninfo", "port") or "") or 9090
 
-       jsonreq4 = utl.exec("(echo /status | nc 127.0.0.1 " .. v4_port .. " | sed -n '/^[}{ ]/p') 2>/dev/null" )
-       jsonreq6 = utl.exec("(echo /status | nc ::1 " .. v6_port .. " | sed -n '/^[}{ ]/p') 2>/dev/null")
+       jsonreq4 = utl.exec("(echo /status | nc 127.0.0.1 %d | sed -n '/^[}{ ]/p') 2>/dev/null" % v4_port)
+       jsonreq6 = utl.exec("(echo /status | nc ::1 %d | sed -n '/^[}{ ]/p') 2>/dev/null" % v6_port)
        http.prepare_content("application/json")
        if not jsonreq4 or jsonreq4 == "" then
                jsonreq4 = "{}"
@@ -150,7 +150,7 @@ function action_neigh(json)
        for _, dev in ipairs(devices) do
                for _, net in ipairs(dev:get_wifinets()) do
                        local radio = net:get_device()
-                       assoclist[#assoclist+1] = {} 
+                       assoclist[#assoclist+1] = {}
                        assoclist[#assoclist]['ifname'] = net:ifname()
                        assoclist[#assoclist]['network'] = net:network()[1]
                        assoclist[#assoclist]['device'] = radio and radio:name() or nil
@@ -165,7 +165,7 @@ function action_neigh(json)
                local mac = ""
                local ip
                local neihgt = {}
-               
+
                if resolve == "1" then
                        hostname = nixio.getnameinfo(v.remoteIP, nil, 100)
                        if hostname then
@@ -350,11 +350,11 @@ function fetch_jsoninfo(otable)
        local IpVersion = uci:get_first("olsrd", "olsrd","IpVersion")
        local jsonreq4 = ""
        local jsonreq6 = ""
-       local v4_port = uci:get("olsrd", "olsrd_jsoninfo", "port") or 9090
-       local v6_port = uci:get("olsrd6", "olsrd_jsoninfo", "port") or 9090
+       local v4_port = tonumber(uci:get("olsrd", "olsrd_jsoninfo", "port") or "") or 9090
+       local v6_port = tonumber(uci:get("olsrd6", "olsrd_jsoninfo", "port") or "") or 9090
 
-       jsonreq4 = utl.exec("(echo /" .. otable .. " | nc 127.0.0.1 " .. v4_port .. " | sed -n '/^[}{ ]/p') 2>/dev/null")
-       jsonreq6 = utl.exec("(echo /" .. otable .. " | nc ::1 " .. v6_port .. " | sed -n '/^[}{ ]/p') 2>/dev/null")
+       jsonreq4 = utl.exec("(echo /%s | nc 127.0.0.1 %d | sed -n '/^[}{ ]/p') 2>/dev/null" %{ otable, v4_port })
+       jsonreq6 = utl.exec("(echo /%s | nc ::1 %d | sed -n '/^[}{ ]/p') 2>/dev/null" %{ otable, v6_port })
        local jsondata4 = {}
        local jsondata6 = {}
        local data4 = {}
index 719145b..ccad531 100644 (file)
@@ -26,9 +26,9 @@ uci:foreach( "openvpn_recipes", "openvpn_recipe",
 )
 
 function s.getPID(section) -- Universal function which returns valid pid # or nil
-       local pid = sys.exec("%s | grep -w %s | grep openvpn | grep -v grep | awk '{print $1}'" % { psstring,section} )
-       if pid and #pid > 0 and tonumber(pid) ~= nil then
-               return tonumber(pid)
+       local pid = sys.exec("%s | grep -w [o]penvpn(%s)" % { psstring, section })
+       if pid and #pid > 0 then
+               return tonumber(pid:match("^%d+"))
        else
                return nil
        end
index b4fdbd5..af7a3a3 100644 (file)
@@ -9,7 +9,7 @@ function index()
        entry({"admin", "services", "splash", "splashtext" }, form("splash/splashtext"), _("Splashtext"), 10)
 
        local e
-       
+
        e = node("splash")
        e.target = call("action_dispatch")
 
@@ -82,7 +82,7 @@ function action_activate()
                        end
                end)
 
-               if blacklisted then     
+               if blacklisted then
                        luci.http.redirect(luci.dispatcher.build_url("splash" ,"blocked"))
                else
                        local id = tostring(mac):gsub(':', ''):lower()
@@ -106,7 +106,7 @@ function action_status_admin()
        local uci = luci.model.uci.cursor_state()
        local macs = luci.http.formvaluetable("save")
 
-       local changes = { 
+       local changes = {
                whitelist = { },
                blacklist = { },
                lease     = { },
@@ -129,22 +129,22 @@ function action_status_admin()
 
        if #changes.whitelist > 0 then
                os.execute("luci-splash whitelist %s >/dev/null"
-                       % table.concat(changes.whitelist))
+                       % util.shellquote(table.concat(changes.whitelist)))
        end
 
        if #changes.blacklist > 0 then
                os.execute("luci-splash blacklist %s >/dev/null"
-                       % table.concat(changes.blacklist))
+                       % util.shellquote(table.concat(changes.blacklist)))
        end
 
        if #changes.lease > 0 then
                os.execute("luci-splash lease %s >/dev/null"
-                       % table.concat(changes.lease))
+                       % util.shellquote(table.concat(changes.lease)))
        end
 
        if #changes.remove > 0 then
                os.execute("luci-splash remove %s >/dev/null"
-                       % table.concat(changes.remove))
+                       % util.shellquote(table.concat(changes.remove)))
        end
 
        luci.template.render("admin_status/splash", { is_admin = true })
index 2870dbe..9ec9f3a 100755 (executable)
@@ -36,6 +36,10 @@ function call(cmd)
        os.execute(cmd)
 end
 
+function esc(str)
+       return utl.shellquote(str)
+end
+
 
 function lock()
        call("lock /var/run/luci_splash.lock")
@@ -84,14 +88,14 @@ end
 
 function get_physdev(interface)
        local dev
-       dev = utl.trim(sys.exec(". /lib/functions/network.sh; network_get_device IFNAME '" ..  interface .. "'; echo $IFNAME"))
+       dev = utl.trim(sys.exec(". /lib/functions/network.sh; network_get_device IFNAME %s; echo $IFNAME" % esc(interface)))
        return dev
 end
 
 
 
 function get_filter_handle(parent, direction, device, mac)
-       local input = utl.split(sys.exec('/usr/sbin/tc filter show dev ' .. device .. ' parent ' .. parent) or {})
+       local input = utl.split(sys.exec('/usr/sbin/tc filter show dev %s parent %s' %{ esc(device), esc(parent) }) or {})
        local tbl = {}
        local handle
        for k, v in pairs(input) do
@@ -264,7 +268,7 @@ function main(argv)
                                elseif whitelist_macs[mac] then
                                        print("Removing %s from whitelist" % mac)
                                        remove_whitelist(mac)
-                                       whitelist_macs[mac] = nil                                       
+                                       whitelist_macs[mac] = nil
                                elseif blacklist_macs[mac] then
                                        print("Removing %s from blacklist" % mac)
                                        remove_blacklist(mac)
@@ -295,7 +299,7 @@ function main(argv)
                print("\n  luci-splash remove <MAC-or-IP>\n    Remove given address from the lease-, black- or whitelist")
                print("")
 
-               os.exit(1)      
+               os.exit(1)
        end
 end
 
@@ -338,8 +342,8 @@ function ipt_delete_all(args, comp, off)
                        off[r.table] = off[r.table] or { }
                        off[r.table][r.chain] = off[r.table][r.chain] or 0
 
-                       exec("iptables -t %q -D %q %d 2>/dev/null"
-                               %{ r.table, r.chain, r.index - off[r.table][r.chain] })
+                       exec("iptables -t %s -D %s %d 2>/dev/null"
+                               %{ esc(r.table), esc(r.chain), r.index - off[r.table][r.chain] })
 
                        off[r.table][r.chain] = off[r.table][r.chain] + 1
                end
@@ -353,8 +357,8 @@ function ipt6_delete_all(args, comp, off)
                        off[r.table] = off[r.table] or { }
                        off[r.table][r.chain] = off[r.table][r.chain] or 0
 
-                       exec("ip6tables -t %q -D %q %d 2>/dev/null"
-                               %{ r.table, r.chain, r.index - off[r.table][r.chain] })
+                       exec("ip6tables -t %s -D %s %d 2>/dev/null"
+                               %{ esc(r.table), esc(r.chain), r.index - off[r.table][r.chain] })
 
                        off[r.table][r.chain] = off[r.table][r.chain] + 1
                end
@@ -460,13 +464,13 @@ function remove_whitelist_tc(mac)
                        end
                        local handle = get_filter_handle('ffff:', 'src', device, mac)
                        if handle then
-                               exec('tc filter del dev "%s" parent ffff: protocol ip prio 1 handle %s u32' % { device, handle })
+                               exec('tc filter del dev %s parent ffff: protocol ip prio 1 handle %s u32' % { esc(device), esc(handle) })
                        else
                                print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
                        end
                        local handle = get_filter_handle('1:', 'dest', device, mac)
                        if handle then
-                               exec('tc filter del dev "%s" parent 1:0 protocol ip prio 1 handle %s u32' % { device, handle })
+                               exec('tc filter del dev %s parent 1:0 protocol ip prio 1 handle %s u32' % { esc(device), esc(handle) })
                        else
                                print('Warning! Could not get a handle for %s parent 1:0 on interface %s' % { mac, device })
                        end
@@ -492,37 +496,37 @@ function add_lease_rule(mac, ipaddr, device)
                id = get_id(ipaddr)
        end
 
-       exec("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j RETURN" % mac)
+       exec("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %s -j RETURN" % esc(mac))
 
        -- Mark incoming packets to a splashed host
        -- for ipv4 - by iptables and destination
        if id and device then
-               exec("iptables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 0x1%s -m comment --comment %s" % {ipaddr, id, mac:upper()})
+               exec("iptables -t mangle -I luci_splash_mark_in -d %s -j MARK --set-mark 0x1%s -m comment --comment %s" % { esc(ipaddr), esc(id), esc(mac:upper())})
        end
 
        --for ipv6: need to use the mac here
 
        if has_ipv6 then
-               exec("ip6tables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
+               exec("ip6tables -t mangle -I luci_splash_mark_out -m mac --mac-source %s -j MARK --set-mark 79" % esc(mac))
                if id and device and tonumber(limit_down) then
-                       exec("tc filter add dev %s parent 1:0 protocol ipv6 prio 1 u32 match ether dst %s classid 1:%s" % {device, mac:lower(), id})
+                       exec("tc filter add dev %s parent 1:0 protocol ipv6 prio 1 u32 match ether dst %s classid 1:%s" % { esc(device), esc(mac:lower()), esc(id) })
                end
        end
 
 
        if device and tonumber(limit_up) > 0 then
-               exec('tc filter add dev "%s" parent ffff: protocol all prio 2 u32 match ether src %s police rate %skbit mtu 6k burst 6k drop' % {device, mac, limit_up})
+               exec('tc filter add dev %s parent ffff: protocol all prio 2 u32 match ether src %s police rate %skbit mtu 6k burst 6k drop' % { esc(device), esc(mac), esc(limit_up) })
        end
 
        if id and device and tonumber(limit_down) > 0 then
-               exec("tc class add dev %s parent 1: classid 1:0x%s htb rate %skbit" % { device, id, limit_down })
-               exec("tc qdisc add dev %s parent 1:%s sfq perturb 10" % { device, id })
+               exec("tc class add dev %s parent 1: classid 1:0x%s htb rate %skbit" % { esc(device), esc(id), esc(limit_down) })
+               exec("tc qdisc add dev %s parent 1:%s sfq perturb 10" % { esc(device), esc(id) })
        end
 
-       exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
-       exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
+       exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %s -j RETURN" % esc(mac))
+       exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %s -j RETURN" % esc(mac))
        if has_ipv6 then
-               exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
+               exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %s -j RETURN" % esc(mac))
        end
 end
 
@@ -548,32 +552,32 @@ function remove_lease_rule(mac, ipaddr, device, limit_up, limit_down)
        if device and tonumber(limit_up) > 0 then
                local handle = get_filter_handle('ffff:', 'src', device, mac)
                if handle then
-                       exec('tc filter del dev "%s" parent ffff: protocol all prio 2 handle %s u32 police rate %skbit mtu 6k burst 6k drop' % {device, handle, limit_up})
+                       exec('tc filter del dev %s parent ffff: protocol all prio 2 handle %s u32 police rate %skbit mtu 6k burst 6k drop' % { esc(device), esc(handle), esc(limit_up) })
                else
                        print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
                end
        end
        -- remove clients class
        if device and id then
-               exec('tc class del dev "%s" classid 1:%s' % {device, id})
-               exec('tc filter del dev "%s" parent 1:0 prio 1' % device) -- ipv6 rule
-               --exec('tc qdisc del dev "%s" parent 1:%s sfq perturb 10' % { device, id })
+               exec('tc class del dev %s classid 1:%s' % { esc(device), esc(id) })
+               exec('tc filter del dev %s parent 1:0 prio 1' % esc(device)) -- ipv6 rule
+               --exec('tc qdisc del dev %s parent 1:%s sfq perturb 10' % { esc(device), esc(id) })
        end
 end
 
 
 -- Add whitelist rules
 function add_whitelist_rule(mac)
-       exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
-       exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
+       exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %s -j RETURN" % esc(mac))
+       exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %s -j RETURN" % esc(mac))
        if has_ipv6 then
-               exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
+               exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %s -j RETURN" % esc(mac))
        end
         uci:foreach("luci_splash", "iface", function(s)
                local device = get_physdev(s['.name'])
                if device and device ~= "" then
-                       exec('tc filter add dev "%s" parent ffff: protocol ip prio 1 u32 match ether src %s police pass' % { device, mac })
-                       exec('tc filter add dev "%s" parent 1:0 protocol ip prio 1 u32 match ether dst %s classid 1:1' % { device, mac })
+                       exec('tc filter add dev %s parent ffff: protocol ip prio 1 u32 match ether src %s police pass' % { esc(device), esc(mac) })
+                       exec('tc filter add dev %s parent 1:0 protocol ip prio 1 u32 match ether dst %s classid 1:1' % { esc(device), esc(mac) })
                end
         end)
 end
@@ -581,9 +585,9 @@ end
 
 -- Add blacklist rules
 function add_blacklist_rule(mac)
-       exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
+       exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %s -j DROP" % esc(mac))
        if has_ipv6 then
-               exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
+               exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %s -j DROP" % esc(mac))
        end
 end
 
@@ -596,15 +600,15 @@ function sync()
 
        -- Current leases in state files
        local leases = uci:get_all("luci_splash_leases")
-       
+
        -- Convert leasetime to seconds
        local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
-       
+
        -- Clean state file
        uci:load("luci_splash_leases")
        uci:revert("luci_splash_leases")
 
-        
+
        local blackwhitelist = uci:get_all("luci_splash")
        local whitelist_total = 0
        local whitelist_online = 0
@@ -628,7 +632,7 @@ function sync()
                                 end
 
                                -- Rewrite state
-                               uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {             
+                               uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {
                                        mac    = v.mac,
                                        ipaddr = v.ipaddr,
                                        device = v.device,
@@ -639,7 +643,7 @@ function sync()
                        end
                end
        end
-       
+
        -- Whitelist, Blacklist
        for _, s in utl.spairs(blackwhitelist,
                function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
@@ -666,7 +670,7 @@ function sync()
 
        -- ToDo:
         -- include a new field "leases_online" in stats to differ between active clients and leases:
-        -- update_stats(leasecount, leases_online, whitelist_online, whitelist_total, blacklist_online, blacklist_total) later: 
+        -- update_stats(leasecount, leases_online, whitelist_online, whitelist_total, blacklist_online, blacklist_total) later:
         update_stats(leases_online, whitelist_online, whitelist_total, blacklist_online, blacklist_total)
 
        uci:save("luci_splash_leases")
index e29a2e1..47e1696 100644 (file)
@@ -87,7 +87,7 @@ function Graph._rrdtool( self, def, rrd )
        fs.mkdirr( dir )
 
        -- construct commandline
-       local cmdline = "rrdtool graph"
+       local cmdline = { "rrdtool", "graph" }
 
        -- copy default arguments to def stack
        for i, opt in ipairs(self.args) do
@@ -102,15 +102,11 @@ function Graph._rrdtool( self, def, rrd )
                        opt = opt:gsub( "{file}", rrd )
                end
 
-               if opt:match("[^%w]") then
-                       cmdline = cmdline .. " '" .. opt .. "'"
-               else
-                       cmdline = cmdline .. " " .. opt
-               end
+               cmdline[#cmdline+1] = luci.util.shellquote(opt)
        end
 
        -- execute rrdtool
-       local rrdtool = io.popen( cmdline )
+       local rrdtool = io.popen(table.concat(cmdline, " "))
        rrdtool:close()
 end
 
index d43a887..2ba9ddd 100644 (file)
@@ -13,7 +13,11 @@ if luci.http.formvalue("frame") == "1" then
                end)
 
        local data = false
-       local wget = io.popen("wget -qO- http://%s:%s" % { addr, port })
+       local wget = io.popen("wget -qO- http://%s:%s" %{
+               luci.util.shellquote(addr),
+               luci.util.shellquote(port)
+       })
+
        if wget then
                while true do
                        local l = wget:read("*l")
@@ -30,7 +34,10 @@ if luci.http.formvalue("frame") == "1" then
 
        if not data then
                luci.http.write(translate("Failed to retrieve statistics from url:"))
-               luci.http.write(" http://%s:%s" % { addr, port })
+               luci.http.write(" http://%s:%s" %{
+                       luci.util.pcdata(addr),
+                       luci.util.pcdata(port)
+               })
        end
 
        return
@@ -43,7 +50,7 @@ end
 <div class="cbi-map">
        <h2 name="content"><%:Tinyproxy Status%></h2>
        <div class="cbi-section">
-               <iframe src="<%=REQUESTURL%>?frame=1" style="width:100%; height:350px; border:none"></iframe>
+               <iframe src="<%=REQUEST_URI%>?frame=1" style="width:100%; height:350px; border:none"></iframe>
        </div>
 </div>
 
index e485708..95a0ef4 100644 (file)
@@ -21,7 +21,7 @@ end
 function act_status()
        local uci = luci.model.uci.cursor()
        local lease_file = uci:get("upnpd", "config", "upnp_lease_file")
-       
+
        local ipt = io.popen("iptables --line-numbers -t nat -xnvL MINIUPNPD 2>/dev/null")
        if ipt then
                local upnpf = lease_file and io.open(lease_file, "r")
@@ -39,7 +39,7 @@ function act_status()
                                        num     = tonumber(num)
                                        extport = tonumber(extport)
                                        intport = tonumber(intport)
-                                       
+
                                        if upnpf then
                                                local uln = upnpf:read("*l")
                                                if uln then descr = uln:match(string.format("^%s:%d:%s:%d:%%d*:(.*)$", proto:upper(), extport, intaddr, intport)) end
@@ -76,7 +76,7 @@ function act_delete(num)
 
                local lease_file = uci:get("upnpd", "config", "upnp_lease_file")
                if lease_file and nixio.fs.access(lease_file) then
-                       luci.sys.call("sed -i -e '%dd' %q" %{ idx, lease_file })
+                       luci.sys.call("sed -i -e '%dd' %s" %{ idx, luci.util.shellquote(lease_file) })
                end
 
                luci.http.status(200, "OK")
index 2b8d9ff..42d7d24 100644 (file)
@@ -21,12 +21,13 @@ style = (style and #style > 0) and style or "s"
 -- render image
 --
 if iface then
-       style = style:gsub("[^%w]", "")
-       iface = iface:gsub("[^%w%.%-%_]", "")
-
        luci.http.prepare_content("image/png")
 
-       local png = io.popen("vnstati -i '%s' '-%s' -o -" % { iface, style })
+       local png = io.popen("vnstati -i %s -%s -o -" %{
+               utl.shellquote(iface),
+               utl.shellquote(style)
+       })
+
        luci.http.write(png:read("*a"))
        png:close()
 
@@ -89,7 +90,7 @@ dbdir = dbdir or "/var/lib/vnstat"
 <%
        end
      end
-   end 
+   end
 %>
 
 <% if empty then %>
index d40dde0..43b87dd 100644 (file)
@@ -1,6 +1,7 @@
 -- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org>
 -- Licensed to the public under the Apache License 2.0.
 
+local utl = require "luci.util"
 local sys = require "luci.sys"
 local ipc = require "luci.ip"
 local fs  = require "nixio.fs"
@@ -69,8 +70,8 @@ function host.write(self, s, val)
                if util == "/usr/bin/etherwake" then
                        local iface = luci.http.formvalue("cbid.wol.1.iface")
                        local broadcast = luci.http.formvalue("cbid.wol.1.broadcast")
-                       cmd = "%s -D%s %s %q" %{
-                               util, (iface ~= "" and " -i %q" % iface or ""),
+                       cmd = "%s -D%s %s %q 2>&1" %{
+                               util, (iface ~= "" and " -i %s" % utl.shellquote(iface) or ""),
                                (broadcast == "1" and " -b" or ""), mac
                        }
                else
@@ -78,7 +79,7 @@ function host.write(self, s, val)
                end
 
                local msg = "<p><strong>%s</strong><br /><br /><code>%s<br /><br />" %{
-                       translate("Starting WoL utility:"), cmd
+                       translate("Starting WoL utility:"), utl.pcdata(cmd)
                }
 
                local p = io.popen(cmd .. " 2>&1")
index d40ec34..6c35372 100644 (file)
@@ -218,12 +218,13 @@ var cbi_validators = {
                        ((ipv4only == 1) && cbi_validators.ip4addr.apply(this));
        },
 
-       'hostname': function()
+       'hostname': function(strict)
        {
                if (this.length <= 253)
-                       return (this.match(/^[a-zA-Z0-9]+$/) != null ||
+                       return (this.match(/^[a-zA-Z0-9_]+$/) != null ||
                                (this.match(/^[a-zA-Z0-9_][a-zA-Z0-9_\-.]*[a-zA-Z0-9]$/) &&
-                                this.match(/[^0-9.]/)));
+                                this.match(/[^0-9.]/))) &&
+                              (!strict || !this.match(/^_/));
 
                return false;
        },
index 55cdf8a..99113e0 100644 (file)
@@ -199,13 +199,13 @@ function macaddr(val)
        return ip.checkmac(val) and true or false
 end
 
-function hostname(val)
+function hostname(val, strict)
        if val and (#val < 254) and (
           val:match("^[a-zA-Z_]+$") or
           (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and
            val:match("[^0-9%.]"))
        ) then
-               return true
+               return (not strict or not val:match("^_"))
        end
        return false
 end
index 16b3254..c93fd78 100644 (file)
@@ -346,15 +346,23 @@ function dispatch(request)
                   ifattr      = function(...) return _ifattr(...) end;
                   attr        = function(...) return _ifattr(true, ...) end;
                   url         = build_url;
-               }, {__index=function(table, key)
+               }, {__index=function(tbl, key)
                        if key == "controller" then
                                return build_url()
                        elseif key == "REQUEST_URI" then
                                return build_url(unpack(ctx.requestpath))
+                       elseif key == "FULL_REQUEST_URI" then
+                               local url = { http.getenv("SCRIPT_NAME"), http.getenv("PATH_INFO") }
+                               local query = http.getenv("QUERY_STRING")
+                               if query and #query > 0 then
+                                       url[#url+1] = "?"
+                                       url[#url+1] = query
+                               end
+                               return table.concat(url, "")
                        elseif key == "token" then
                                return ctx.authtoken
                        else
-                               return rawget(table, key) or _G[key]
+                               return rawget(tbl, key) or _G[key]
                        end
                end})
        end
@@ -884,7 +892,7 @@ end
 function cbi(model, config)
        return {
                type = "cbi",
-               post = { ["cbi.submit"] = "1" },
+               post = { ["cbi.submit"] = true },
                config = config,
                model = model,
                target = _cbi
@@ -930,7 +938,7 @@ end
 function form(model)
        return {
                type = "cbi",
-               post = { ["cbi.submit"] = "1" },
+               post = { ["cbi.submit"] = true },
                model = model,
                target = _form
        }
index e653b03..e27ea52 100644 (file)
@@ -20,12 +20,14 @@ module "luci.model.ipkg"
 
 -- Internal action function
 local function _action(cmd, ...)
-       local pkg = ""
+       local cmdline = { ipkg, cmd }
+
+       local k, v
        for k, v in pairs({...}) do
-               pkg = pkg .. " '" .. v:gsub("'", "") .. "'"
+               cmdline[#cmdline+1] = util.shellquote(v)
        end
 
-       local c = "%s %s %s >/tmp/opkg.stdout 2>/tmp/opkg.stderr" %{ ipkg, cmd, pkg }
+       local c = "%s >/tmp/opkg.stdout 2>/tmp/opkg.stderr" % table.concat(cmdline, " ")
        local r = os.execute(c)
        local e = fs.readfile("/tmp/opkg.stderr")
        local o = fs.readfile("/tmp/opkg.stdout")
@@ -74,17 +76,17 @@ local function _parselist(rawdata)
 end
 
 -- Internal lookup function
-local function _lookup(act, pkg)
-       local cmd = ipkg .. " " .. act
+local function _lookup(cmd, pkg)
+       local cmdline = { ipkg, cmd }
        if pkg then
-               cmd = cmd .. " '" .. pkg:gsub("'", "") .. "'"
+               cmdline[#cmdline+1] = util.shellquote(pkg)
        end
 
        -- OPKG sometimes kills the whole machine because it sucks
        -- Therefore we have to use a sucky approach too and use
        -- tmpfiles instead of directly reading the output
        local tmpfile = os.tmpname()
-       os.execute(cmd .. (" >%s 2>/dev/null" % tmpfile))
+       os.execute("%s >%s 2>/dev/null" %{ table.concat(cmdline, " "), tmpfile })
 
        local data = _parselist(io.lines(tmpfile))
        os.remove(tmpfile)
@@ -123,9 +125,12 @@ end
 
 -- List helper
 local function _list(action, pat, cb)
-       local fd = io.popen(ipkg .. " " .. action ..
-               (pat and (" '%s'" % pat:gsub("'", "")) or ""))
+       local cmdline = { ipkg, action }
+       if pat then
+               cmdline[#cmdline+1] = util.shellquote(pat)
+       end
 
+       local fd = io.popen(table.concat(cmdline, " "))
        if fd then
                local name, version, sz, desc
                while true do
index 577c6cd..bbd9b4c 100644 (file)
@@ -2,13 +2,12 @@
 -- Licensed to the public under the Apache License 2.0.
 
 local os    = require "os"
-local uci   = require "uci"
 local util  = require "luci.util"
 local table = require "table"
 
 
 local setmetatable, rawget, rawset = setmetatable, rawget, rawset
-local require, getmetatable = require, getmetatable
+local require, getmetatable, assert = require, getmetatable, assert
 local error, pairs, ipairs = error, pairs, ipairs
 local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
 
@@ -20,151 +19,410 @@ local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack
 -- reloaded.
 module "luci.model.uci"
 
-cursor = uci.cursor
-
-APIVERSION = uci.APIVERSION
+local ERRSTR = {
+       "Invalid command",
+       "Invalid argument",
+       "Method not found",
+       "Entry not found",
+       "No data",
+       "Permission denied",
+       "Timeout",
+       "Not supported",
+       "Unknown error",
+       "Connection failed"
+}
+
+
+function cursor()
+       return _M
+end
 
 function cursor_state()
-       return cursor(nil, "/var/state")
+       return _M
 end
 
+function substate(self)
+       return self
+end
 
-inst = cursor()
-inst_state = cursor_state()
 
-local Cursor = getmetatable(inst)
+function get_confdir(self)
+       return "/etc/config"
+end
 
-function Cursor.apply(self, configlist, command)
-       configlist = self:_affected(configlist)
-       if command then
-               return { "/sbin/luci-reload", unpack(configlist) }
-       else
-               return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
-                       % table.concat(configlist, " "))
-       end
+function get_savedir(self)
+       return "/tmp/.uci"
+end
+
+function set_confdir(self, directory)
+       return false
 end
 
+function set_savedir(self, directory)
+       return false
+end
 
--- returns a boolean whether to delete the current section (optional)
-function Cursor.delete_all(self, config, stype, comparator)
-       local del = {}
 
-       if type(comparator) == "table" then
-               local tbl = comparator
-               comparator = function(section)
-                       for k, v in pairs(tbl) do
-                               if section[k] ~= v then
-                                       return false
+function load(self, config)
+       return true
+end
+
+function save(self, config)
+       return true
+end
+
+function unload(self, config)
+       return true
+end
+
+
+function changes(self, config)
+       local rv = util.ubus("uci", "changes", { config = config })
+       local res = {}
+
+       if type(rv) == "table" and type(rv.changes) == "table" then
+               local package, changes
+               for package, changes in pairs(rv.changes) do
+                       res[package] = {}
+
+                       local _, change
+                       for _, change in ipairs(changes) do
+                               local operation, section, option, value = unpack(change)
+                               if option and value and operation ~= "add" then
+                                       res[package][section] = res[package][section] or { }
+
+                                       if operation == "list-add" then
+                                               local v = res[package][section][option]
+                                               if type(v) == "table" then
+                                                       v[#v+1] = value or ""
+                                               elseif v ~= nil then
+                                                       res[package][section][option] = { v, value }
+                                               else
+                                                       res[package][section][option] = { value }
+                                               end
+                                       else
+                                               res[package][section][option] = value or ""
+                                       end
+                               else
+                                       res[package][section] = res[package][section] or {}
+                                       res[package][section][".type"] = option or ""
                                end
                        end
-                       return true
                end
        end
 
-       local function helper (section)
+       return res
+end
+
+
+function revert(self, config)
+       local _, err = util.ubus("uci", "revert", { config = config })
+       return (err == nil), ERRSTR[err]
+end
+
+function commit(self, config)
+       local _, err = util.ubus("uci", "commit", { config = config })
+       return (err == nil), ERRSTR[err]
+end
+
+--[[
+function apply(self, configs, command)
+       local _, config
 
-               if not comparator or comparator(section) then
-                       del[#del+1] = section[".name"]
+       assert(not command, "Apply command not supported anymore")
+
+       if type(configs) == "table" then
+               for _, config in ipairs(configs) do
+                       util.ubus("service", "event", {
+                               type = "config.change",
+                               data = { package = config }
+                       })
                end
        end
+end
+]]
+
+
+function foreach(self, config, stype, callback)
+       if type(callback) == "function" then
+               local rv, err = util.ubus("uci", "get", {
+                       config = config,
+                       type   = stype
+               })
+
+               if type(rv) == "table" and type(rv.values) == "table" then
+                       local sections = { }
+                       local res = false
+                       local index = 1
+
+                       local _, section
+                       for _, section in pairs(rv.values) do
+                               section[".index"] = section[".index"] or index
+                               sections[index] = section
+                               index = index + 1
+                       end
 
-       self:foreach(config, stype, helper)
+                       table.sort(sections, function(a, b)
+                               return a[".index"] < b[".index"]
+                       end)
 
-       for i, j in ipairs(del) do
-               self:delete(config, j)
+                       for _, section in ipairs(sections) do
+                               local continue = callback(section)
+                               res = true
+                               if continue == false then
+                                       break
+                               end
+                       end
+                       return res
+               else
+                       return false, ERRSTR[err] or "No data"
+               end
+       else
+               return false, "Invalid argument"
        end
 end
 
-function Cursor.section(self, config, type, name, values)
-       local stat = true
-       if name then
-               stat = self:set(config, name, type)
+function get(self, config, section, option)
+       if section == nil then
+               return nil
+       elseif type(option) == "string" and option:byte(1) ~= 46 then
+               local rv, err = util.ubus("uci", "get", {
+                       config  = config,
+                       section = section,
+                       option  = option
+               })
+
+               if type(rv) == "table" then
+                       return rv.value or nil
+               elseif err then
+                       return false, ERRSTR[err]
+               else
+                       return nil
+               end
+       elseif option == nil then
+               local values = self:get_all(config, section)
+               if values then
+                       return values[".type"], values[".name"]
+               else
+                       return nil
+               end
        else
-               name = self:add(config, type)
-               stat = name and true
+               return false, "Invalid argument"
        end
+end
+
+function get_all(self, config, section)
+       local rv, err = util.ubus("uci", "get", {
+               config  = config,
+               section = section
+       })
 
-       if stat and values then
-               stat = self:tset(config, name, values)
+       if type(rv) == "table" and type(rv.values) == "table" then
+               return rv.values
+       elseif err then
+               return false, ERRSTR[err]
+       else
+               return nil
        end
+end
 
-       return stat and name
+function get_bool(self, ...)
+       local val = self:get(...)
+       return (val == "1" or val == "true" or val == "yes" or val == "on")
 end
 
-function Cursor.tset(self, config, section, values)
-       local stat = true
-       for k, v in pairs(values) do
-               if k:sub(1, 1) ~= "." then
-                       stat = stat and self:set(config, section, k, v)
+function get_first(self, config, stype, option, default)
+       local rv = default
+
+       self:foreach(conf, stype, function(s)
+               local val = not option and s[".name"] or s[option]
+
+               if type(default) == "number" then
+                       val = tonumber(val)
+               elseif type(default) == "boolean" then
+                       val = (val == "1" or val == "true" or
+                              val == "yes" or val == "on")
                end
-       end
-       return stat
-end
 
-function Cursor.get_bool(self, ...)
-       local val = self:get(...)
-       return ( val == "1" or val == "true" or val == "yes" or val == "on" )
+               if val ~= nil then
+                       rv = val
+                       return false
+               end
+       end)
+
+       return rv
 end
 
-function Cursor.get_list(self, config, section, option)
+function get_list(self, config, section, option)
        if config and section and option then
                local val = self:get(config, section, option)
-               return ( type(val) == "table" and val or { val } )
+               return (type(val) == "table" and val or { val })
        end
-       return {}
+       return { }
 end
 
-function Cursor.get_first(self, conf, stype, opt, def)
-       local rv = def
 
-       self:foreach(conf, stype,
-               function(s)
-                       local val = not opt and s['.name'] or s[opt]
+function section(self, config, stype, name, values)
+       local rv, err = util.ubus("uci", "add", {
+               config = config,
+               type   = stype,
+               name   = name,
+               values = values
+       })
 
-                       if type(def) == "number" then
-                               val = tonumber(val)
-                       elseif type(def) == "boolean" then
-                               val = (val == "1" or val == "true" or
-                                      val == "yes" or val == "on")
+       if type(rv) == "table" then
+               return rv.section
+       elseif err then
+               return false, ERRSTR[err]
+       else
+               return nil
+       end
+end
+
+
+function add(self, config, stype)
+       return self:section(config, stype)
+end
+
+function set(self, config, section, option, value)
+       if value == nil then
+               local sname, err = self:section(config, option, section)
+               return (not not sname), err
+       else
+               local _, err = util.ubus("uci", "set", {
+                       config  = config,
+                       section = section,
+                       values  = { [option] = value }
+               })
+               return (err == nil), ERRSTR[err]
+       end
+end
+
+function set_list(self, config, section, option, value)
+       if section == nil or option == nil then
+               return false
+       elseif value == nil or (type(value) == "table" and #value == 0) then
+               return self:delete(config, section, option)
+       elseif type(value) == "table" then
+               return self:set(config, section, option, value)
+       else
+               return self:set(config, section, option, { value })
+       end
+end
+
+function tset(self, config, section, values)
+       local _, err = util.ubus("uci", "set", {
+               config  = config,
+               section = section,
+               values  = values
+       })
+       return (err == nil), ERRSTR[err]
+end
+
+function reorder(self, config, section, index)
+       local sections
+
+       if type(section) == "string" and type(index) == "number" then
+               local pos = 0
+
+               sections = { }
+
+               self:foreach(config, nil, function(s)
+                       if pos == index then
+                               pos = pos + 1
                        end
 
-                       if val ~= nil then
-                               rv = val
-                               return false
+                       if s[".name"] ~= section then
+                               pos = pos + 1
+                               sections[pos] = s[".name"]
+                       else
+                               sections[index + 1] = section
                        end
                end)
+       elseif type(section) == "table" then
+               sections = section
+       else
+               return false, "Invalid argument"
+       end
 
-       return rv
+       local _, err = util.ubus("uci", "order", {
+               config   = config,
+               sections = sections
+       })
+
+       return (err == nil), ERRSTR[err]
 end
 
-function Cursor.set_list(self, config, section, option, value)
-       if config and section and option then
-               if not value or #value == 0 then
-                       return self:delete(config, section, option)
+
+function delete(self, config, section, option)
+       local _, err = util.ubus("uci", "delete", {
+               config  = config,
+               section = section,
+               option  = option
+       })
+       return (err == nil), ERRSTR[err]
+end
+
+function delete_all(self, config, stype, comparator)
+       local _, err
+       if type(comparator) == "table" then
+               _, err = util.ubus("uci", "delete", {
+                       config = config,
+                       type   = stype,
+                       match  = comparator
+               })
+       elseif type(comparator) == "function" then
+               local rv = util.ubus("uci", "get", {
+                       config = config,
+                       type   = stype
+               })
+
+               if type(rv) == "table" and type(rv.values) == "table" then
+                       local sname, section
+                       for sname, section in pairs(rv.values) do
+                               if comparator(section) then
+                                       _, err = util.ubus("uci", "delete", {
+                                               config  = config,
+                                               section = sname
+                                       })
+                               end
+                       end
                end
-               return self:set(
-                       config, section, option,
-                       ( type(value) == "table" and value or { value } )
-               )
+       elseif comparator == nil then
+               _, err = util.ubus("uci", "delete", {
+                       config  = config,
+                       type    = stype
+               })
+       else
+               return false, "Invalid argument"
        end
-       return false
+
+       return (err == nil), ERRSTR[err]
 end
 
--- Return a list of initscripts affected by configuration changes.
-function Cursor._affected(self, configlist)
-       configlist = type(configlist) == "table" and configlist or {configlist}
 
-       local c = cursor()
-       c:load("ucitrack")
+function apply(self, configlist, command)
+       configlist = self:_affected(configlist)
+       if command then
+               return { "/sbin/luci-reload", unpack(configlist) }
+       else
+               return os.execute("/sbin/luci-reload %s >/dev/null 2>&1"
+                       % util.shellquote(table.concat(configlist, " ")))
+       end
+end
+
+-- Return a list of initscripts affected by configuration changes.
+function _affected(self, configlist)
+       configlist = type(configlist) == "table" and configlist or { configlist }
 
        -- Resolve dependencies
-       local reloadlist = {}
+       local reloadlist = { }
 
        local function _resolve_deps(name)
-               local reload = {name}
-               local deps = {}
+               local reload = { name }
+               local deps = { }
 
-               c:foreach("ucitrack", name,
+               self:foreach("ucitrack", name,
                        function(section)
                                if section.affects then
                                        for i, aff in ipairs(section.affects) do
@@ -173,7 +431,9 @@ function Cursor._affected(self, configlist)
                                end
                        end)
 
+               local i, dep
                for i, dep in ipairs(deps) do
+                       local j, add
                        for j, add in ipairs(_resolve_deps(dep)) do
                                reload[#reload+1] = add
                        end
@@ -183,7 +443,9 @@ function Cursor._affected(self, configlist)
        end
 
        -- Collect initscripts
+       local j, config
        for j, config in ipairs(configlist) do
+               local i, e
                for i, e in ipairs(_resolve_deps(config)) do
                        if not util.contains(reloadlist, e) then
                                reloadlist[#reloadlist+1] = e
@@ -193,44 +455,3 @@ function Cursor._affected(self, configlist)
 
        return reloadlist
 end
-
--- curser, means it the parent unloads or loads configs, the sub state will
--- do so as well.
-function Cursor.substate(self)
-       Cursor._substates = Cursor._substates or { }
-       Cursor._substates[self] = Cursor._substates[self] or cursor_state()
-       return Cursor._substates[self]
-end
-
-local _load = Cursor.load
-function Cursor.load(self, ...)
-       if Cursor._substates and Cursor._substates[self] then
-               _load(Cursor._substates[self], ...)
-       end
-       return _load(self, ...)
-end
-
-local _unload = Cursor.unload
-function Cursor.unload(self, ...)
-       if Cursor._substates and Cursor._substates[self] then
-               _unload(Cursor._substates[self], ...)
-       end
-       return _unload(self, ...)
-end
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
index 12b20e4..823e207 100644 (file)
@@ -87,10 +87,10 @@ end
 function httpget(url, stream, target)
        if not target then
                local source = stream and io.popen or luci.util.exec
-               return source("wget -qO- '"..url:gsub("'", "").."'")
+               return source("wget -qO- %s" % luci.util.shellquote(url))
        else
-               return os.execute("wget -qO '%s' '%s'" %
-                       {target:gsub("'", ""), url:gsub("'", "")})
+               return os.execute("wget -qO %s %s" %
+                       {luci.util.shellquote(target), luci.util.shellquote(url)})
        end
 end
 
@@ -443,18 +443,11 @@ function user.checkpasswd(username, pass)
 end
 
 function user.setpasswd(username, password)
-       if password then
-               password = password:gsub("'", [['"'"']])
-       end
-
-       if username then
-               username = username:gsub("'", [['"'"']])
-       end
-
-       return os.execute(
-               "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " ..
-               "passwd '" .. username .. "' >/dev/null 2>&1"
-       )
+       return os.execute("(echo %s; sleep 1; echo %s) | passwd %s >/dev/null 2>&1" %{
+               luci.util.shellquote(password),
+               luci.util.shellquote(password),
+               luci.util.shellquote(username)
+       })
 end
 
 
index 5012111..06a9ad4 100644 (file)
@@ -187,7 +187,9 @@ function switch_status(devs)
        local switches = { }
        for dev in devs:gmatch("[^%s,]+") do
                local ports = { }
-               local swc = io.popen("swconfig dev %q show" % dev, "r")
+               local swc = io.popen("swconfig dev %s show"
+                       % luci.util.shellquote(dev), "r")
+
                if swc then
                        local l
                        repeat
index 28c1266..06a889c 100644 (file)
@@ -164,6 +164,10 @@ function striptags(value)
        return value and tparser.striptags(tostring(value))
 end
 
+function shellquote(value)
+       return string.format("'%s'", string.gsub(value or "", "'", "'\\''"))
+end
+
 -- for bash, ash and similar shells single-quoted strings are taken
 -- literally except for single quotes (which terminate the string)
 -- (and the exception noted below for dash (-) at the start of a
@@ -656,7 +660,7 @@ function checklib(fullpathexe, wantedlib)
        if not haveldd or not haveexe then
                return false
        end
-       local libs = exec("/usr/bin/ldd " .. fullpathexe)
+       local libs = exec(string.format("/usr/bin/ldd %s", shellquote(fullpathexe)))
        if not libs then
                return false
        end
index 949aeb2..79a17a2 100644 (file)
@@ -83,6 +83,15 @@ Strip HTML tags from given string.
 ]]
 
 ---[[
+Safely quote value for use in shell commands.
+
+@class function
+@name shellquote
+@param value  String containing the value to quote
+@return Single-quote enclosed string with embedded quotes escaped
+]]
+
+---[[
 Splits given string on a defined separator sequence and return a table
 
 containing the resulting substrings. The optional max parameter specifies
index f6b0f57..b3ec9b7 100644 (file)
@@ -6,7 +6,7 @@
 
 <%+header%>
 
-<form method="post" action="<%=pcdata(luci.http.getenv("REQUEST_URI"))%>">
+<form method="post" action="<%=pcdata(FULL_REQUEST_URI)%>">
        <%- if fuser then %>
        <div class="errorbox"><%:Invalid username and/or password! Please try again.%></div>
        <% end -%>
index d3ed1a5..188f8cb 100644 (file)
@@ -723,7 +723,7 @@ msgstr "自定义软件源"
 msgid ""
 "Custom files (certificates, scripts) may remain on the system. To prevent "
 "this, perform a factory-reset first."
-msgstr ""
+msgstr "自定义文件(证书、脚本)会保留在系统上。若无需保留,请先执行恢复出厂设置。"
 
 msgid ""
 "Customizes the behaviour of the device <abbr title=\"Light Emitting Diode"
index 33f6a67..070a9e6 100644 (file)
@@ -289,7 +289,8 @@ function iface_reconnect(iface)
        local netmd = require "luci.model.network".init()
        local net = netmd:get_network(iface)
        if net then
-               luci.sys.call("env -i /sbin/ifup %q >/dev/null 2>/dev/null" % iface)
+               luci.sys.call("env -i /sbin/ifup %s >/dev/null 2>/dev/null"
+                       % luci.util.shellquote(iface))
                luci.http.status(200, "Reconnected")
                return
        end
@@ -301,7 +302,8 @@ function iface_shutdown(iface)
        local netmd = require "luci.model.network".init()
        local net = netmd:get_network(iface)
        if net then
-               luci.sys.call("env -i /sbin/ifdown %q >/dev/null 2>/dev/null" % iface)
+               luci.sys.call("env -i /sbin/ifdown %s >/dev/null 2>/dev/null"
+                       % luci.util.shellquote(iface))
                luci.http.status(200, "Shutdown")
                return
        end
@@ -313,7 +315,8 @@ function iface_delete(iface)
        local netmd = require "luci.model.network".init()
        local net = netmd:del_network(iface)
        if net then
-               luci.sys.call("env -i /sbin/ifdown %q >/dev/null 2>/dev/null" % iface)
+               luci.sys.call("env -i /sbin/ifdown %s >/dev/null 2>/dev/null"
+                       % luci.util.shellquote(iface))
                luci.http.redirect(luci.dispatcher.build_url("admin/network/network"))
                netmd:commit("network")
                netmd:commit("wireless")
@@ -389,7 +392,7 @@ function diag_command(cmd, addr)
        if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then
                luci.http.prepare_content("text/plain")
 
-               local util = io.popen(cmd % addr)
+               local util = io.popen(cmd % luci.util.shellquote(addr))
                if util then
                        while true do
                                local ln = util:read("*l")
@@ -408,21 +411,21 @@ function diag_command(cmd, addr)
 end
 
 function diag_ping(addr)
-       diag_command("ping -c 5 -W 1 %q 2>&1", addr)
+       diag_command("ping -c 5 -W 1 %s 2>&1", addr)
 end
 
 function diag_traceroute(addr)
-       diag_command("traceroute -q 1 -w 1 -n %q 2>&1", addr)
+       diag_command("traceroute -q 1 -w 1 -n %s 2>&1", addr)
 end
 
 function diag_nslookup(addr)
-       diag_command("nslookup %q 2>&1", addr)
+       diag_command("nslookup %s 2>&1", addr)
 end
 
 function diag_ping6(addr)
-       diag_command("ping6 -c 5 %q 2>&1", addr)
+       diag_command("ping6 -c 5 %s 2>&1", addr)
 end
 
 function diag_traceroute6(addr)
-       diag_command("traceroute6 -q 1 -w 2 -n %q 2>&1", addr)
+       diag_command("traceroute6 -q 1 -w 2 -n %s 2>&1", addr)
 end
index 22e1b7e..3a1c169 100644 (file)
@@ -62,7 +62,9 @@ end
 function action_bandwidth(iface)
        luci.http.prepare_content("application/json")
 
-       local bwc = io.popen("luci-bwc -i %q 2>/dev/null" % iface)
+       local bwc = io.popen("luci-bwc -i %s 2>/dev/null"
+               % luci.util.shellquote(iface))
+
        if bwc then
                luci.http.write("[")
 
@@ -80,7 +82,9 @@ end
 function action_wireless(iface)
        luci.http.prepare_content("application/json")
 
-       local bwc = io.popen("luci-bwc -r %q 2>/dev/null" % iface)
+       local bwc = io.popen("luci-bwc -r %s 2>/dev/null"
+               % luci.util.shellquote(iface))
+
        if bwc then
                luci.http.write("[")
 
index 66d9942..b7b73d7 100644 (file)
@@ -274,7 +274,7 @@ s.anonymous = true
 s.template = "cbi/tblsection"
 
 name = s:option(Value, "name", translate("Hostname"))
-name.datatype = "hostname"
+name.datatype = "hostname('strict')"
 name.rmempty  = true
 
 function name.write(self, section, value)
index 89a73a5..b52dff1 100644 (file)
@@ -5,6 +5,7 @@
 m = Map("network", translate("Switch"), translate("The network ports on this device can be combined to several <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s in which computers can communicate directly with each other. <abbr title=\"Virtual Local Area Network\">VLAN</abbr>s are often used to separate different network segments. Often there is by default one Uplink port for a connection to the next greater network like the internet and other ports for a local network."))
 
 local fs = require "nixio.fs"
+local ut = require "luci.util"
 local nw = require "luci.model.network"
 local switches = { }
 
@@ -74,7 +75,7 @@ m.uci:foreach("network", "switch",
                end
 
                -- Parse some common switch properties from swconfig help output.
-               local swc = io.popen("swconfig dev %q help 2>/dev/null" % switch_name)
+               local swc = io.popen("swconfig dev %s help 2>/dev/null" % ut.shellquote(switch_name))
                if swc then
 
                        local is_port_attr = false
index c0bb380..a574d35 100644 (file)
@@ -63,7 +63,7 @@ function m.parse(map)
        Map.parse(map)
 
        if m:get(wdev:name(), "type") == "mac80211" and new_cc and new_cc ~= old_cc then
-               luci.sys.call("iw reg set %q" % new_cc)
+               luci.sys.call("iw reg set %s" % ut.shellquote(new_cc))
                luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless", arg[1]))
                return
        end