applications/luci-splash: merge splash rework to trunk

This commit is contained in:
Jo-Philipp Wich 2009-06-06 09:41:02 +00:00
parent e684a57a09
commit 970dabd1db
4 changed files with 346 additions and 64 deletions

View file

@ -6,6 +6,8 @@ function index()
node("splash").target = call("action_dispatch") node("splash").target = call("action_dispatch")
node("splash", "activate").target = call("action_activate") node("splash", "activate").target = call("action_activate")
node("splash", "splash").target = template("splash_splash/splash") node("splash", "splash").target = template("splash_splash/splash")
entry({"admin", "status", "splash"}, call("action_status_admin"), "Client-Splash")
end end
function action_dispatch() function action_dispatch()
@ -28,3 +30,69 @@ function action_activate()
luci.http.redirect(luci.dispatcher.build_url()) luci.http.redirect(luci.dispatcher.build_url())
end end
end end
function action_status_admin()
local uci = luci.model.uci.cursor_state()
local macs = luci.http.formvaluetable("save")
local function delete_mac(what, mac)
uci:delete_all("luci_splash", what,
function(s)
return ( s.mac and s.mac:lower() == mac )
end)
end
local function leases(mac)
local leases = { }
uci:foreach("luci_splash", "lease", function(s)
if s.start and s.mac and s.mac:lower() ~= mac then
leases[#leases+1] = {
start = s.start,
mac = s.mac
}
end
end)
uci:revert("luci_splash")
return leases
end
local function commit(leases, no_commit)
if not no_commit then
uci:save("luci_splash")
uci:commit("luci_splash")
end
for _, l in ipairs(leases) do
uci:section("luci_splash", "lease", nil, l)
end
uci:save("luci_splash")
os.execute("/etc/init.d/luci_splash restart")
end
for key, _ in pairs(macs) do
local policy = luci.http.formvalue("policy.%s" % key)
local mac = luci.http.protocol.urldecode(key)
local lslist = leases(policy ~= "kick" and mac)
delete_mac("blacklist", mac)
delete_mac("whitelist", mac)
if policy == "whitelist" or policy == "blacklist" then
uci:section("luci_splash", policy, nil, { mac = mac })
elseif policy == "normal" then
lslist[#lslist+1] = { mac = mac, start = os.time() }
elseif policy == "kick" then
for _, l in ipairs(lslist) do
if l.mac:lower() == mac then l.kicked="1" end
end
end
commit(lslist)
end
luci.template.render("admin_status/splash", { is_admin = true })
end

View file

@ -0,0 +1,189 @@
<%#
LuCI - Lua Configuration Interface
Copyright 2009 Jo-Philipp Wich <xm@leipzig.freifunk.net>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
$Id$
-%>
<%-
local utl = require "luci.util"
local ipt = require "luci.sys.iptparser".IptParser()
local uci = require "luci.model.uci".cursor_state()
local wat = require "luci.tools.webadmin"
local clients = { }
local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime") or 1) * 60 * 60
local leasefile = "/tmp/dhcp.leases"
uci:foreach("dhcp", "dnsmasq",
function(s)
if s.leasefile then leasefile = s.leasefile end
end)
uci:foreach("luci_splash", "lease",
function(s)
if s.start and s.mac then
clients[s.mac:lower()] = {
start = tonumber(s.start),
limit = ( tonumber(s.start) + leasetime ),
mac = s.mac:upper(),
policy = "normal",
packets = 0,
bytes = 0,
kicked = s.kicked and true or false
}
end
end)
for _, r in ipairs(ipt:find({table="nat", chain="luci_splash_leases"})) do
if r.options and #r.options >= 2 and r.options[1] == "MAC" then
if not clients[r.options[2]:lower()] then
clients[r.options[2]:lower()] = {
start = 0,
limit = 0,
mac = r.options[2]:upper(),
policy = ( r.target == "RETURN" ) and "whitelist" or "blacklist",
packets = 0,
bytes = 0
}
end
end
end
for _, r in ipairs(ipt:find({table="filter", chain="luci_splash_counter"})) do
if r.options and #r.options >= 2 and r.options[1] == "MAC" then
local c = clients[r.options[2]:lower()]
if c and c.packets == 0 then
c.bytes = tonumber(r.bytes)
c.packets = tonumber(r.packets)
end
end
end
uci:foreach("luci_splash", "whitelist",
function(s)
if s.mac and clients[s.mac:lower()] then
clients[s.mac:lower()].policy="whitelist"
end
end)
uci:foreach("luci_splash", "blacklist",
function(s)
if s.mac and clients[s.mac:lower()] then
clients[s.mac:lower()].policy=(s.kicked and "kicked" or "blacklist")
end
end)
if luci.fs.access(leasefile) then
for l in io.lines(leasefile) do
local time, mac, ip, name = l:match("^(%d+) (%S+) (%S+) (%S+)")
if time and mac and ip then
local c = clients[mac:lower()]
if c then
c.ip = ip
c.hostname = ( name ~= "*" ) and name or nil
end
end
end
end
for i, a in ipairs(luci.sys.net.arptable()) do
local c = clients[a["HW address"]:lower()]
if c and not c.ip then
c.ip = a["IP address"]
end
end
local function showmac(mac)
if not is_admin then
mac = mac:gsub("(%S%S:%S%S):%S%S:%S%S:(%S%S:%S%S)", "%1:XX:XX:%2")
end
return mac
end
-%>
<%+header%>
<div id="cbi-splash-leases" class="cbi-map">
<h2><a id="content" name="content"><%:ff_splash Client-Splash%></a></h2>
<fieldset id="cbi-table-table" class="cbi-section">
<legend><%:ff_splash_clients Active Clients%></legend>
<div class="cbi-section-node">
<% if is_admin then %><form action="<%=REQUEST_URI%>" method="post"><% end %>
<table class="cbi-section-table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"><%:ff_splash_hostname Hostname%></th>
<th class="cbi-section-table-cell"><%:ff_splash_ip IP Address%></th>
<th class="cbi-section-table-cell"><%:ff_splash_mac MAC Address%></th>
<th class="cbi-section-table-cell"><%:ff_splash_timeleft Time remaining%></th>
<th class="cbi-section-table-cell"><%:ff_splash_traffic Outgoing traffic%></th>
<th class="cbi-section-table-cell"><%:ff_splash_policy Policy%></th>
</tr>
<%-
local count = 0
for _, c in utl.spairs(clients,
function(a,b)
if clients[a].policy == clients[b].policy then
return (clients[a].start > clients[b].start)
else
return (clients[a].policy > clients[b].policy)
end
end)
do
if c.ip then
count = count + 1
-%>
<tr class="cbi-section-table-row cbi-rowstyle-<%=2-(count%2)%>">
<td class="cbi-section-table-cell"><%=c.hostname or "<em>" .. translate("ff_splash_unknown", "unknown") .. "</em>"%></td>
<td class="cbi-section-table-cell"><%=c.ip or "<em>" .. translate("ff_splash_unknown", "unknown") .. "</em>"%></td>
<td class="cbi-section-table-cell"><%=showmac(c.mac)%></td>
<td class="cbi-section-table-cell"><%=
(c.limit >= os.time()) and wat.date_format(c.limit-os.time()) or
(c.policy ~= "normal") and "-" or "<em>" .. translate("ff_splash_expired", "expired") .. "</em>"
%></td>
<td class="cbi-section-table-cell"><%=wat.byte_format(c.bytes)%></td>
<td class="cbi-section-table-cell">
<% if is_admin then %>
<select name="policy.<%=c.mac:lower()%>" style="width:200px">
<option value="whitelist"<%=c.policy=="whitelist" and ' selected="selected"'%>><%:ff_splash_whitelisted whitelisted%></option>
<option value="normal"<%=c.policy=="normal" and not c.kicked and ' selected="selected"'%>><%:ff_splash_splashed splashed%></option>
<option value="blacklist"<%=c.policy=="blacklist" and ' selected="selected"'%>><%:ff_splash_blacklisted blacklisted%></option>
<% if c.policy == "normal" then -%>
<option value="kick"<%=c.kicked and ' selected="selected"'%>><%:ff_splash_tempblock temporarily blocked%> (<%=wat.date_format(c.limit-os.time())%>)</option>
<%- end %>
</select>
<input type="submit" class="cbi-button cbi-button-save" name="save.<%=c.mac:lower()%>" value="<%:save Save%>" />
<% else %>
<%=c.policy%>
<% end %>
</td>
</tr>
<%-
end
end
if count == 0 then
-%>
<tr class="cbi-section-table-row">
<td colspan="7" class="cbi-section-table-cell">
<br /><em><%:ff_splash_noclients No clients connected%></em><br />
</td>
</tr>
<%- end -%>
</table>
<% if is_admin then %></form><% end %>
</div>
</fieldset>
</div>
<%+footer%>

View file

@ -35,14 +35,24 @@ blacklist_add() {
local cfg="$1" local cfg="$1"
config_get mac "$cfg" mac config_get mac "$cfg" mac
[ -n "$mac" ] && iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j DROP [ -n "$mac" ] && {
iptables -I luci_splash_counter -m mac --mac-source "$mac" -j RETURN
iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j DROP
}
} }
whitelist_add() { whitelist_add() {
local cfg="$1" local cfg="$1"
config_get mac "$cfg" mac config_get mac "$cfg" mac
[ -n "$mac" ] && iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j RETURN config_get ban "$cfg" kicked
ban=${ban:+DROP}
[ -n "$mac" ] && {
iptables -I luci_splash_counter -m mac --mac-source "$mac" -j RETURN
iptables -t nat -I luci_splash_leases -m mac --mac-source "$mac" -j "${ban:-RETURN}"
}
} }
boot() { boot() {
@ -74,6 +84,7 @@ start() {
config_load luci_splash config_load luci_splash
### Create subchains ### Create subchains
iptables -N luci_splash_counter
iptables -t nat -N luci_splash_portal iptables -t nat -N luci_splash_portal
iptables -t nat -N luci_splash_leases iptables -t nat -N luci_splash_leases
iptables -t nat -N luci_splash_prerouting iptables -t nat -N luci_splash_prerouting
@ -85,6 +96,8 @@ start() {
config_foreach iface_add iface config_foreach iface_add iface
### Build the portal rule ### Build the portal rule
iptables -I INPUT -j luci_splash_counter
iptables -I FORWARD -j luci_splash_counter
iptables -t nat -A luci_splash_portal -p udp --dport 33434:33523 -j RETURN iptables -t nat -A luci_splash_portal -p udp --dport 33434:33523 -j RETURN
iptables -t nat -A luci_splash_portal -p icmp -j RETURN iptables -t nat -A luci_splash_portal -p icmp -j RETURN
iptables -t nat -A luci_splash_portal -p udp --dport 53 -j RETURN iptables -t nat -A luci_splash_portal -p udp --dport 53 -j RETURN
@ -105,16 +118,20 @@ stop() {
### Clear interface rules ### Clear interface rules
config_load luci_splash config_load luci_splash
config_foreach iface_del iface config_foreach iface_del iface
iptables -D INPUT -j luci_splash_counter
iptables -D FORWARD -j luci_splash_counter
### Clear subchains ### Clear subchains
iptables -t nat -F luci_splash_leases iptables -t nat -F luci_splash_leases
iptables -t nat -F luci_splash_portal iptables -t nat -F luci_splash_portal
iptables -t nat -F luci_splash_prerouting iptables -t nat -F luci_splash_prerouting
iptables -F luci_splash_counter
### Delete subchains ### Delete subchains
iptables -t nat -X luci_splash_leases iptables -t nat -X luci_splash_leases
iptables -t nat -X luci_splash_portal iptables -t nat -X luci_splash_portal
iptables -t nat -X luci_splash_prerouting iptables -t nat -X luci_splash_prerouting
iptables -X luci_splash_counter
sed -ie '/\/usr\/sbin\/luci-splash sync/d' /var/spool/cron/crontabs/root sed -ie '/\/usr\/sbin\/luci-splash sync/d' /var/spool/cron/crontabs/root
} }

View file

@ -1,39 +1,35 @@
#!/usr/bin/lua #!/usr/bin/lua
require("luci.http")
require("luci.util") require("luci.util")
require("luci.model.uci") require("luci.model.uci")
require("luci.sys.iptparser")
-- Init state session -- Init state session
local uci = luci.model.uci.cursor_state() local uci = luci.model.uci.cursor_state()
local ipt = luci.sys.iptparser.IptParser()
function main(argv) function main(argv)
local cmd = argv[1] local cmd = argv[1]
local arg = argv[2] local arg = argv[2]
if cmd == "status" then if cmd == "status" and arg then
if not arg then if islisted("whitelist", arg) then
os.exit(1)
end
if iswhitelisted(arg) then
print("whitelisted") print("whitelisted")
os.exit(0) elseif islisted("blacklist", arg) then
end print("blacklisted")
else
if haslease(arg) then local lease = haslease(arg)
if lease and lease.kicked then
print("kicked")
elseif lease then
print("lease") print("lease")
os.exit(0) else
end
print("unknown") print("unknown")
os.exit(0)
elseif cmd == "add" then
if not arg then
os.exit(1)
end end
end
os.exit(0)
elseif cmd == "add" and arg then
if not haslease(arg) then if not haslease(arg) then
add_lease(arg) add_lease(arg)
else else
@ -41,11 +37,7 @@ function main(argv)
os.exit(2) os.exit(2)
end end
os.exit(0) os.exit(0)
elseif cmd == "remove" then elseif cmd == "remove" and arg then
if not arg then
os.exit(1)
end
remove_lease(arg) remove_lease(arg)
os.exit(0) os.exit(0)
elseif cmd == "sync" then elseif cmd == "sync" then
@ -72,19 +64,10 @@ end
-- Remove a lease from state and invoke remove_rule -- Remove a lease from state and invoke remove_rule
function remove_lease(mac) function remove_lease(mac)
mac = mac:lower() mac = mac:lower()
local del = {} remove_rule(mac)
uci:foreach("luci_splash", "lease", uci:delete_all("luci_splash", "lease",
function (section) function(s) return ( s.mac:lower() == mac ) end)
if section.mac:lower() == mac then
table.insert(del, section[".name"])
end
end)
for i,j in ipairs(del) do
remove_rule(j)
uci:delete("luci_splash", j)
end
uci:save("luci_splash") uci:save("luci_splash")
end end
@ -92,38 +75,56 @@ end
-- Add an iptables rule -- Add an iptables rule
function add_rule(mac) function add_rule(mac)
os.execute("iptables -I luci_splash_counter -m mac --mac-source '"..mac.."'")
return os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source '"..mac.."' -j RETURN") return os.execute("iptables -t nat -I luci_splash_leases -m mac --mac-source '"..mac.."' -j RETURN")
end end
-- Remove an iptables rule -- Remove an iptables rule
function remove_rule(mac) function remove_rule(mac)
return os.execute("iptables -t nat -D luci_splash_leases -m mac --mac-source '"..mac.."' -j RETURN") for _, r in ipairs(ipt:find({table="filter", chain="luci_splash_counter"})) do
if r.options and #r.options >= 2 and r.options[1] == "MAC" and
r.options[2]:lower() == mac:lower()
then
os.execute("iptables -D luci_splash_counter -m mac --mac-source %q -j %s"
%{ mac, r.target })
end
end
for _, r in ipairs(ipt:find({table="nat", chain="luci_splash_leases"})) do
if r.options and #r.options >= 2 and r.options[1] == "MAC" and
r.options[2]:lower() == mac:lower()
then
os.execute("iptables -t nat -D luci_splash_leases -m mac --mac-source %q -j %s"
%{ mac, r.target })
end
end
ipt:resync()
end end
-- Check whether a MAC-Address is listed in the lease state list -- Check whether a MAC-Address is listed in the lease state list
function haslease(mac) function haslease(mac)
mac = mac:lower() mac = mac:lower()
local stat = false local lease = nil
uci:foreach("luci_splash", "lease", uci:foreach("luci_splash", "lease",
function (section) function (section)
if section.mac:lower() == mac then if section.mac:lower() == mac then
stat = true lease = section
return
end end
end) end)
return stat return lease
end end
-- Check whether a MAC-Address is whitelisted -- Check whether a MAC-Address is in given list
function iswhitelisted(mac) function islisted(what, mac)
mac = mac:lower() mac = mac:lower()
uci:foreach("luci_splash", "whitelist", uci:foreach("luci_splash", what,
function (section) function (section)
if section.mac:lower() == mac then if section.mac:lower() == mac then
stat = true stat = true
@ -137,9 +138,13 @@ end
-- Returns a list of MAC-Addresses for which a rule is existing -- Returns a list of MAC-Addresses for which a rule is existing
function listrules() function listrules()
local cmd = "iptables -t nat -L luci_splash_leases | grep RETURN |" local macs = { }
cmd = cmd .. "egrep -io [0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+:[0-9a-f]+" for i, r in ipairs(ipt:find({table="nat", chain="luci_splash_leases"})) do
return luci.util.split(luci.util.exec(cmd)) if r.options and #r.options >= 2 and r.options[1] == "MAC" then
macs[r.options[2]:lower()] = true
end
end
return luci.util.keys(macs)
end end
@ -169,10 +174,13 @@ function sync()
-- Rewrite state -- Rewrite state
uci:section("luci_splash", "lease", nil, { uci:section("luci_splash", "lease", nil, {
mac = v.mac, mac = v.mac,
start = v.start start = v.start,
kicked = v.kicked
}) })
written[v.mac:lower()] = 1 written[v.mac:lower()] = 1
end end
elseif v[".type"] == "whitelist" or v[".type"] == "blacklist" then
written[v.mac:lower()] = 1
end end
end end