treewide: filter shell arguments through shellquote() where applicable
[project/luci.git] / applications / luci-app-splash / root / usr / sbin / luci-splash
index 6b1e41e..9ec9f3a 100755 (executable)
@@ -2,15 +2,13 @@
 
 utl = require "luci.util"
 sys = require "luci.sys"
+ipc = require "luci.ip"
 
-require("luci.model.uci")
-require("luci.sys.iptparser")
 
 -- Init state session
-local uci = luci.model.uci.cursor_state()
-local ipt = luci.sys.iptparser.IptParser()
-local net = sys.net
-local fs = require "luci.fs"
+local uci = require "luci.model.uci".cursor_state()
+local ipt = require "luci.sys.iptparser".IptParser()
+local fs = require "nixio.fs"
 local ip = require "luci.ip"
 
 local debug = false
@@ -38,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")
@@ -86,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
@@ -139,6 +141,36 @@ function ipvalid(ipaddr)
        return false
 end
 
+function mac_to_ip(mac)
+       local ipaddr = nil
+       ipc.neighbors({ family = 4 }, function(n)
+               if n.mac == mac and n.dest then
+                       ipaddr = n.dest:string()
+               end
+       end)
+       return ipaddr
+end
+
+function mac_to_dev(mac)
+       local dev = nil
+       ipc.neighbors({ family = 4 }, function(n)
+               if n.mac == mac and n.dev then
+                       dev = n.dev
+               end
+       end)
+       return dev
+end
+
+function ip_to_mac(ip)
+       local mac = nil
+       ipc.neighbors({ family = 4 }, function(n)
+               if n.mac and n.dest and n.dest:equal(ip) then
+                       mac = n.mac
+               end
+       end)
+       return mac
+end
+
 function main(argv)
        local cmd = table.remove(argv, 1)
        local arg = argv[1]
@@ -157,7 +189,6 @@ function main(argv)
 
                lock()
 
-               local arp_cache      = net.arptable()
                local leased_macs    = get_known_macs("lease")
                local blacklist_macs = get_known_macs("blacklist")
                local whitelist_macs = get_known_macs("whitelist")
@@ -167,17 +198,12 @@ function main(argv)
                        if adr:find(":") then
                                mac = adr:lower()
                        else
-                               for _, e in ipairs(arp_cache) do
-                                       if e["IP address"] == adr then
-                                               mac = e["HW address"]:lower()
-                                               break
-                                       end
-                               end
+                               mac = ip_to_mac(adr)
                        end
 
                        if mac and cmd == "add-rules" then
                                if leased_macs[mac] then
-                                       add_lease(mac, arp_cache, true)
+                                       add_lease(mac, true)
                                elseif blacklist_macs[mac] then
                                        add_blacklist_rule(mac)
                                elseif whitelist_macs[mac] then
@@ -242,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)
@@ -273,17 +299,8 @@ 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)      
-       end
-end
-
--- Get current arp cache
-function get_arpcache()
-       local arpcache = { }
-       for _, entry in ipairs(net.arptable()) do
-               arpcache[entry["HW address"]:lower()] = { entry["Device"]:lower(), entry["IP address"]:lower() }
+               os.exit(1)
        end
-       return arpcache
 end
 
 -- Get a list of known mac addresses
@@ -325,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
@@ -340,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
@@ -355,17 +372,11 @@ function convert_mac_to_secname(mac)
 end
 
 -- Add a lease to state and invoke add_rule
-function add_lease(mac, arp, no_uci)
+function add_lease(mac, no_uci)
        mac = mac:lower()
 
        -- Get current ip address
-       local ipaddr
-       for _, entry in ipairs(arp or net.arptable()) do
-               if entry["HW address"]:lower() == mac then
-                       ipaddr = entry["IP address"]
-                       break
-               end
-       end
+       local ipaddr = mac_to_ip(mac)
 
        -- Add lease if there is an ip addr
        if ipaddr then
@@ -453,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
@@ -485,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
 
@@ -541,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
@@ -574,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
 
@@ -589,16 +600,14 @@ 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 arpcache = get_arpcache()
 
        local blackwhitelist = uci:get_all("luci_splash")
        local whitelist_total = 0
@@ -618,12 +627,12 @@ function sync()
                                leasecount = leasecount + 1
 
                                 -- only count leases_online for connected clients
-                                if arpcache[v.mac] then
+                               if mac_to_ip(v.mac) then
                                        leases_online = leases_online + 1
                                 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,
@@ -634,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
@@ -643,7 +652,7 @@ function sync()
                        whitelist_total = whitelist_total + 1
                        if s.mac then
                                local mac = s.mac:lower()
-                               if arpcache[mac] then
+                               if mac_to_ip(mac) then
                                        whitelist_online = whitelist_online + 1
                                end
                        end
@@ -652,7 +661,7 @@ function sync()
                        blacklist_total = blacklist_total + 1
                        if s.mac then
                                local mac = s.mac:lower()
-                               if arpcache[mac] then
+                               if mac_to_ip(mac) then
                                        blacklist_online = blacklist_online + 1
                                end
                        end
@@ -661,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")
@@ -693,7 +702,6 @@ end
 
 -- Show client info
 function list()
-       local arpcache = get_arpcache()
        -- Find traffic usage
        local function traffic(lease)
                local traffic_in  = 0
@@ -722,12 +730,11 @@ function list()
                if s[".type"] == "lease" and s.mac then
                        local ti, to = traffic(s)
                        local mac = s.mac:lower()
-                       local arp = arpcache[mac]
                        print(string.format(
                                "%-17s  %-15s  %-9s  %3dm  %-7s  %7dKB  %7dKB",
                                mac, s.ipaddr, "leased",
                                math.floor(( os.time() - tonumber(s.start) ) / 60),
-                               arp and arp[1] or "?", ti, to
+                               mac_to_dev(mac) or "?", ti, to
                        ))
                end
        end
@@ -738,11 +745,10 @@ function list()
        ) do
                if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
                        local mac = s.mac:lower()
-                       local arp = arpcache[mac]
                        print(string.format(
                                "%-17s  %-15s  %-9s  %4s  %-7s  %9s  %9s",
-                               mac, arp and arp[2] or "?", s[".type"],
-                               "- ", arp and arp[1] or "?", "-", "-"
+                               mac, mac_to_ip(mac) or "?", s[".type"],
+                               "- ", mac_to_dev(mac) or "?", "-", "-"
                        ))
                end
        end