Rework LuCI build system
[project/luci.git] / applications / luci-app-splash / root / usr / sbin / luci-splash
1 #!/usr/bin/lua
2
3 utl = require "luci.util"
4 sys = require "luci.sys"
5
6 require("luci.model.uci")
7 require("luci.sys.iptparser")
8
9 -- Init state session
10 local uci = luci.model.uci.cursor_state()
11 local ipt = luci.sys.iptparser.IptParser()
12 local net = sys.net
13 local fs = require "luci.fs"
14 local ip = require "luci.ip"
15
16 local debug = false
17
18 local has_ipv6 = fs.access("/proc/net/ipv6_route") and fs.access("/usr/sbin/ip6tables")
19
20 function exec(cmd)
21         -- executes a cmd and gets its output
22         if debug then
23                 local ret = sys.exec(cmd)
24                 print('+ ' .. cmd)
25                 if ret and ret ~= "" then
26                         print(ret)
27                 end
28         else
29                 local ret = sys.exec(cmd .. " &> /dev/null")
30         end
31 end
32
33 function call(cmd)
34         -- just calls a command
35         if debug then
36                 print('+ ' .. cmd)
37         end
38         os.execute(cmd)
39 end
40
41
42 function lock()
43         call("lock /var/run/luci_splash.lock")
44 end
45
46 function unlock()
47         call("lock -u /var/run/luci_splash.lock")
48 end
49
50 function get_id(ip)
51         local o3, o4 = ip:match("[0-9]+%.[0-9]+%.([0-9]+)%.([0-9]+)")
52         if o3 and 04 then
53                 return string.format("%02X%s", tonumber(o3), "") .. string.format("%02X%s", tonumber(o4), "")
54         else
55                 return false
56         end
57 end
58
59 function update_stats(leased, whitelisted, whitelisttotal, blacklisted, blacklisttotal)
60         local leases = uci:get_all("luci_splash_leases", "stats")
61         uci:delete("luci_splash_leases", "stats")
62         uci:section("luci_splash_leases", "stats", "stats", {
63                 leases    = leased or (leases and leases.leases) or 0,
64                 whitelisttotal = whitelisttotal or (leased and leases.whitelisttotal) or 0,
65                 whitelistonline = whitelisted or (leases and leases.whitelistonline) or 0,
66                 blacklisttotal = blacklisttotal or (leases and leases.blacklisttotal) or 0,
67                 blacklistonline = blacklisted or (leases and leases.blacklistonline) or 0,
68         })
69         uci:save("luci_splash_leases")
70 end
71
72
73 function get_device_for_ip(ipaddr)
74         local dev
75         uci:foreach("network", "interface", function(s)
76                 if s.ipaddr and s.netmask then
77                         local network = ip.IPv4(s.ipaddr, s.netmask)
78                         if network:contains(ip.IPv4(ipaddr)) then
79                                 -- this should be rewritten to luci functions if possible
80                                 dev = utl.trim(sys.exec(". /lib/functions/network.sh; network_get_device IFNAME '" ..  s['.name'] .. "'; echo $IFNAME"))
81                         end
82                 end
83         end)
84         return dev
85 end
86
87 function get_physdev(interface)
88         local dev
89         dev = utl.trim(sys.exec(". /lib/functions/network.sh; network_get_device IFNAME '" ..  interface .. "'; echo $IFNAME"))
90         return dev
91 end
92
93
94
95 function get_filter_handle(parent, direction, device, mac)
96         local input = utl.split(sys.exec('/usr/sbin/tc filter show dev ' .. device .. ' parent ' .. parent) or {})
97         local tbl = {}
98         local handle
99         for k, v in pairs(input) do
100                 handle = v:match('filter protocol ip pref %d+ u32 fh (%d*:%d*:%d*) order') or v:match('filter protocol all pref %d+ u32 fh (%d*:%d*:%d*) order')
101                 if handle then
102                         local mac, mac1, mac2, mac3, mac4, mac5, mac6
103                         if direction == 'src' then
104                                 mac1, mac2, mac3, mac4 = input[k+1]:match('match ([%a%d][%a%d])([%a%d][%a%d])([%a%d][%a%d])([%a%d][%a%d])/ffffffff')
105                                 mac5, mac6 = input[k+2]:match('match ([%a%d][%a%d])([%a%d][%a%d])0000/ffff0000')
106                         else
107                                 mac1, mac2 = input[k+1]:match('match 0000([%a%d][%a%d])([%a%d][%a%d])/0000ffff')
108                                 mac3, mac4, mac5, mac6 = input[k+2]:match('match ([%a%d][%a%d])([%a%d][%a%d])([%a%d][%a%d])([%a%d][%a%d])/ffffffff')
109                         end
110                         if mac1 and mac2 and mac3 and mac4 and mac5 and mac6 then
111                                 mac = "%s:%s:%s:%s:%s:%s" % { mac1, mac2, mac3, mac4, mac5, mac6 }
112                                 tbl[mac] = handle
113                         end
114                 end
115         end
116         if tbl[mac] then
117                 handle = tbl[mac]
118         end
119         return handle
120 end
121
122 function macvalid(mac)
123         if mac and mac:match(
124                 "^[a-fA-F0-9][a-fA-F0-9]:[a-fA-F0-9][a-fA-F0-9]:" ..
125                 "[a-fA-F0-9][a-fA-F0-9]:[a-fA-F0-9][a-fA-F0-9]:" ..
126                 "[a-fA-F0-9][a-fA-F0-9]:[a-fA-F0-9][a-fA-F0-9]$"
127         ) then
128                 return true
129         end
130
131         return false
132 end
133
134 function ipvalid(ipaddr)
135         if ipaddr then
136                 return ip.IPv4(ipaddr) and true or false
137         end
138
139         return false
140 end
141
142 function main(argv)
143         local cmd = table.remove(argv, 1)
144         local arg = argv[1]
145
146         limit_up = (tonumber(uci:get("luci_splash", "general", "limit_up")) or 0) * 8
147         limit_down = (tonumber(uci:get("luci_splash", "general", "limit_down")) or 0) * 8
148
149         if ( cmd == "lease" or cmd == "add-rules" or cmd == "remove" or
150              cmd == "whitelist" or cmd == "blacklist" or cmd == "status" ) and #argv > 0
151         then
152                 if not (macvalid(arg) or ipvalid(arg)) then
153                         print("Invalid argument. The second argument must " ..
154                                 "be a valid IPv4 or Mac Address.")
155                         os.exit(1)
156                 end
157
158                 lock()
159
160                 local arp_cache      = net.arptable()
161                 local leased_macs    = get_known_macs("lease")
162                 local blacklist_macs = get_known_macs("blacklist")
163                 local whitelist_macs = get_known_macs("whitelist")
164
165                 for i, adr in ipairs(argv) do
166                         local mac = nil
167                         if adr:find(":") then
168                                 mac = adr:lower()
169                         else
170                                 for _, e in ipairs(arp_cache) do
171                                         if e["IP address"] == adr then
172                                                 mac = e["HW address"]:lower()
173                                                 break
174                                         end
175                                 end
176                         end
177
178                         if mac and cmd == "add-rules" then
179                                 if leased_macs[mac] then
180                                         add_lease(mac, arp_cache, true)
181                                 elseif blacklist_macs[mac] then
182                                         add_blacklist_rule(mac)
183                                 elseif whitelist_macs[mac] then
184                                         add_whitelist_rule(mac)
185                                 end
186                         elseif mac and cmd == "status" then
187                                 print(leased_macs[mac] and "lease"
188                                         or whitelist_macs[mac] and "whitelist"
189                                         or blacklist_macs[mac] and "blacklist"
190                                         or "new")
191                         elseif mac and ( cmd == "whitelist" or cmd == "blacklist" or cmd == "lease" ) then
192                                 if cmd ~= "lease" and leased_macs[mac] then
193                                         print("Removing %s from leases" % mac)
194                                         remove_lease(mac)
195                                         leased_macs[mac] = nil
196                                 end
197
198                                 if cmd ~= "whitelist" and whitelist_macs[mac] then
199                                         if cmd == "lease" then
200                                                 print('%s is whitelisted. Remove it before you can lease it.' % mac)
201                                         else
202                                                 print("Removing %s from whitelist" % mac)
203                                                 remove_whitelist(mac)
204                                                 whitelist_macs[mac] = nil
205                                         end
206                                 end
207
208                                 if cmd == "whitelist" and leased_macs[mac] then
209                                         print("Removing %s from leases" % mac)
210                                         remove_lease(mac)
211                                         leased_macs[mac] = nil
212                                 end
213
214                                 if cmd ~= "blacklist" and blacklist_macs[mac] then
215                                         print("Removing %s from blacklist" % mac)
216                                         remove_blacklist(mac)
217                                         blacklist_macs[mac] = nil
218                                 end
219
220                                 if cmd == "lease" and not leased_macs[mac] then
221                                         if not whitelist_macs[mac] then
222                                                 print("Adding %s to leases" % mac)
223                                                 add_lease(mac)
224                                                 leased_macs[mac] = true
225                                         end
226                                 elseif cmd == "whitelist" and not whitelist_macs[mac] then
227                                         print("Adding %s to whitelist" % mac)
228                                         add_whitelist(mac)
229                                         whitelist_macs[mac] = true
230                                 elseif cmd == "blacklist" and not blacklist_macs[mac] then
231                                         print("Adding %s to blacklist" % mac)
232                                         add_blacklist(mac)
233                                         blacklist_macs[mac] = true
234                                 else
235                                         print("The mac %s is already %sed" %{ mac, cmd })
236                                 end
237                         elseif mac and cmd == "remove" then
238                                 if leased_macs[mac] then
239                                         print("Removing %s from leases" % mac)
240                                         remove_lease(mac)
241                                         leased_macs[mac] = nil
242                                 elseif whitelist_macs[mac] then
243                                         print("Removing %s from whitelist" % mac)
244                                         remove_whitelist(mac)
245                                         whitelist_macs[mac] = nil                                       
246                                 elseif blacklist_macs[mac] then
247                                         print("Removing %s from blacklist" % mac)
248                                         remove_blacklist(mac)
249                                         blacklist_macs[mac] = nil
250                                 else
251                                         print("The mac %s is not known" % mac)
252                                 end
253
254                         else
255                                 print("Can not find mac for ip %s" % argv[i])
256                         end
257                 end
258                 unlock()
259                 os.exit(0)
260         elseif cmd == "sync" then
261                 sync()
262                 os.exit(0)
263         elseif cmd == "list" then
264                 list()
265                 os.exit(0)
266         else
267                 print("Usage:")
268                 print("\n  luci-splash list\n    List connected, black- and whitelisted clients")
269                 print("\n  luci-splash sync\n    Synchronize firewall rules and clear expired leases")
270                 print("\n  luci-splash lease <MAC-or-IP>\n    Create a lease for the given address")
271                 print("\n  luci-splash blacklist <MAC-or-IP>\n    Add given address to blacklist")
272                 print("\n  luci-splash whitelist <MAC-or-IP>\n    Add given address to whitelist")
273                 print("\n  luci-splash remove <MAC-or-IP>\n    Remove given address from the lease-, black- or whitelist")
274                 print("")
275
276                 os.exit(1)      
277         end
278 end
279
280 -- Get current arp cache
281 function get_arpcache()
282         local arpcache = { }
283         for _, entry in ipairs(net.arptable()) do
284                 arpcache[entry["HW address"]:lower()] = { entry["Device"]:lower(), entry["IP address"]:lower() }
285         end
286         return arpcache
287 end
288
289 -- Get a list of known mac addresses
290 function get_known_macs(list)
291         local leased_macs = { }
292
293         if not list or list == "lease" then
294                 uci:foreach("luci_splash_leases", "lease", function(s)
295                         if s.mac then
296                                 leased_macs[s.mac:lower()] = true
297                         end
298                 end)
299         end
300
301         if not list or list == "whitelist" then
302                 uci:foreach("luci_splash", "whitelist", function(s)
303                         if s.mac then
304                                 leased_macs[s.mac:lower()] = true
305                         end
306                 end)
307         end
308
309         if not list or list == "blacklist" then
310                 uci:foreach("luci_splash", "blacklist", function(s)
311                         if s.mac then
312                                 leased_macs[s.mac:lower()] = true
313                         end
314                 end)
315         end
316         return leased_macs
317 end
318
319
320 -- Helper to delete iptables rules
321 function ipt_delete_all(args, comp, off)
322         off = off or { }
323         for i, r in ipairs(ipt:find(args)) do
324                 if comp == nil or comp(r) then
325                         off[r.table] = off[r.table] or { }
326                         off[r.table][r.chain] = off[r.table][r.chain] or 0
327
328                         exec("iptables -t %q -D %q %d 2>/dev/null"
329                                 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
330
331                         off[r.table][r.chain] = off[r.table][r.chain] + 1
332                 end
333         end
334 end
335
336 function ipt6_delete_all(args, comp, off)
337         off = off or { }
338         for i, r in ipairs(ipt:find(args)) do
339                 if comp == nil or comp(r) then
340                         off[r.table] = off[r.table] or { }
341                         off[r.table][r.chain] = off[r.table][r.chain] or 0
342
343                         exec("ip6tables -t %q -D %q %d 2>/dev/null"
344                                 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
345
346                         off[r.table][r.chain] = off[r.table][r.chain] + 1
347                 end
348         end
349 end
350
351
352 -- Convert mac to uci-compatible section name
353 function convert_mac_to_secname(mac)
354         return string.gsub(mac, ":", "")
355 end
356
357 -- Add a lease to state and invoke add_rule
358 function add_lease(mac, arp, no_uci)
359         mac = mac:lower()
360
361         -- Get current ip address
362         local ipaddr
363         for _, entry in ipairs(arp or net.arptable()) do
364                 if entry["HW address"]:lower() == mac then
365                         ipaddr = entry["IP address"]
366                         break
367                 end
368         end
369
370         -- Add lease if there is an ip addr
371         if ipaddr then
372                 local device = get_device_for_ip(ipaddr)
373                 if not no_uci then
374                         local leased = uci:get("luci_splash_leases", "stats", "leases")
375                         if type(tonumber(leased)) == "number" then
376                                 update_stats(leased + 1, nil, nil, nil, nil)
377                         end
378
379                         uci:section("luci_splash_leases", "lease", convert_mac_to_secname(mac), {
380                                 mac    = mac,
381                                 ipaddr = ipaddr,
382                                 device = device,
383                                 limit_up = limit_up,
384                                 limit_down = limit_down,
385                                 start  = os.time()
386                         })
387                         uci:save("luci_splash_leases")
388                 end
389                 add_lease_rule(mac, ipaddr, device)
390         else
391                 print("Found no active IP for %s, lease not added" % mac)
392         end
393 end
394
395
396 -- Remove a lease from state and invoke remove_rule
397 function remove_lease(mac)
398         mac = mac:lower()
399
400         uci:delete_all("luci_splash_leases", "lease",
401                 function(s)
402                         if s.mac:lower() == mac then
403
404                                 local leased = uci:get("luci_splash_leases", "stats", "leases")
405                                 if type(tonumber(leased)) == "number" and tonumber(leased) > 0 then
406                                         update_stats(leased - 1, nil, nil, nil, nil)
407                                 end
408                                 remove_lease_rule(mac, s.ipaddr, s.device, tonumber(s.limit_up), tonumber(s.limit_down))
409                                 return true
410                         end
411                         return false
412                 end)
413
414         uci:save("luci_splash_leases")
415 end
416
417
418 -- Add a whitelist entry
419 function add_whitelist(mac)
420         uci:section("luci_splash", "whitelist", convert_mac_to_secname(mac), { mac = mac })
421         uci:save("luci_splash")
422         uci:commit("luci_splash")
423         add_whitelist_rule(mac)
424 end
425
426
427 -- Add a blacklist entry
428 function add_blacklist(mac)
429         uci:section("luci_splash", "blacklist", convert_mac_to_secname(mac), { mac = mac })
430         uci:save("luci_splash")
431         uci:commit("luci_splash")
432         add_blacklist_rule(mac)
433 end
434
435
436 -- Remove a whitelist entry
437 function remove_whitelist(mac)
438         mac = mac:lower()
439         uci:delete_all("luci_splash", "whitelist",
440                 function(s) return not s.mac or s.mac:lower() == mac end)
441         uci:save("luci_splash")
442         uci:commit("luci_splash")
443         remove_lease_rule(mac)
444         remove_whitelist_tc(mac)
445 end
446
447 function remove_whitelist_tc(mac)
448         uci:foreach("luci_splash", "iface", function(s)
449                 local device = get_physdev(s['.name'])
450                 if device and device ~= "" then
451                         if debug then
452                                 print("Removing whitelist filters for %s interface %s." % {mac, device})
453                         end
454                         local handle = get_filter_handle('ffff:', 'src', device, mac)
455                         if handle then
456                                 exec('tc filter del dev "%s" parent ffff: protocol ip prio 1 handle %s u32' % { device, handle })
457                         else
458                                 print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
459                         end
460                         local handle = get_filter_handle('1:', 'dest', device, mac)
461                         if handle then
462                                 exec('tc filter del dev "%s" parent 1:0 protocol ip prio 1 handle %s u32' % { device, handle })
463                         else
464                                 print('Warning! Could not get a handle for %s parent 1:0 on interface %s' % { mac, device })
465                         end
466                 end
467         end)
468 end
469
470 -- Remove a blacklist entry
471 function remove_blacklist(mac)
472         mac = mac:lower()
473         uci:delete_all("luci_splash", "blacklist",
474                 function(s) return not s.mac or s.mac:lower() == mac end)
475         uci:save("luci_splash")
476         uci:commit("luci_splash")
477         remove_lease_rule(mac)
478 end
479
480
481 -- Add an iptables rule
482 function add_lease_rule(mac, ipaddr, device)
483         local id
484         if ipaddr then
485                 id = get_id(ipaddr)
486         end
487
488         exec("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j RETURN" % mac)
489
490         -- Mark incoming packets to a splashed host
491         -- for ipv4 - by iptables and destination
492         if id and device then
493                 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()})
494         end
495
496         --for ipv6: need to use the mac here
497
498         if has_ipv6 then
499                 exec("ip6tables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
500                 if id and device and tonumber(limit_down) then
501                         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})
502                 end
503         end
504
505
506         if device and tonumber(limit_up) > 0 then
507                 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})
508         end
509
510         if id and device and tonumber(limit_down) > 0 then
511                 exec("tc class add dev %s parent 1: classid 1:0x%s htb rate %skbit" % { device, id, limit_down })
512                 exec("tc qdisc add dev %s parent 1:%s sfq perturb 10" % { device, id })
513         end
514
515         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
516         exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
517         if has_ipv6 then
518                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
519         end
520 end
521
522
523 -- Remove lease, black- or whitelist rules
524 function remove_lease_rule(mac, ipaddr, device, limit_up, limit_down)
525
526         local id
527         if ipaddr then
528                 id = get_id(ipaddr)
529         end
530
531         ipt:resync()
532         ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", mac:upper()}})
533         ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
534         ipt_delete_all({table="filter", chain="luci_splash_filter",   options={"MAC", mac:upper()}})
535         ipt_delete_all({table="nat",    chain="luci_splash_leases",   options={"MAC", mac:upper()}})
536         if has_ipv6 then
537                 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
538                 ipt6_delete_all({table="filter", chain="luci_splash_filter",   options={"MAC", mac:upper()}})
539         end
540
541         if device and tonumber(limit_up) > 0 then
542                 local handle = get_filter_handle('ffff:', 'src', device, mac)
543                 if handle then
544                         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})
545                 else
546                         print('Warning! Could not get a handle for %s parent :ffff on interface %s' % { mac, device })
547                 end
548         end
549         -- remove clients class
550         if device and id then
551                 exec('tc class del dev "%s" classid 1:%s' % {device, id})
552                 exec('tc filter del dev "%s" parent 1:0 prio 1' % device) -- ipv6 rule
553                 --exec('tc qdisc del dev "%s" parent 1:%s sfq perturb 10' % { device, id })
554         end
555 end
556
557
558 -- Add whitelist rules
559 function add_whitelist_rule(mac)
560         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
561         exec("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
562         if has_ipv6 then
563                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
564         end
565         uci:foreach("luci_splash", "iface", function(s)
566                 local device = get_physdev(s['.name'])
567                 if device and device ~= "" then
568                         exec('tc filter add dev "%s" parent ffff: protocol ip prio 1 u32 match ether src %s police pass' % { device, mac })
569                         exec('tc filter add dev "%s" parent 1:0 protocol ip prio 1 u32 match ether dst %s classid 1:1' % { device, mac })
570                 end
571         end)
572 end
573
574
575 -- Add blacklist rules
576 function add_blacklist_rule(mac)
577         exec("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
578         if has_ipv6 then
579                 exec("ip6tables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
580         end
581 end
582
583
584 -- Synchronise leases, remove abandoned rules
585 function sync()
586         lock()
587
588         local time = os.time()
589
590         -- Current leases in state files
591         local leases = uci:get_all("luci_splash_leases")
592         
593         -- Convert leasetime to seconds
594         local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
595         
596         -- Clean state file
597         uci:load("luci_splash_leases")
598         uci:revert("luci_splash_leases")
599
600         
601         local arpcache = get_arpcache()
602
603         local blackwhitelist = uci:get_all("luci_splash")
604         local whitelist_total = 0
605         local whitelist_online = 0
606         local blacklist_total = 0
607         local blacklist_online = 0
608         local leasecount = 0
609         local leases_online = 0
610
611         -- For all leases
612         for k, v in pairs(leases) do
613                 if v[".type"] == "lease" then
614                         if os.difftime(time, tonumber(v.start)) > leasetime then
615                                 -- Remove expired
616                                 remove_lease_rule(v.mac, v.ipaddr, v.device, tonumber(v.limit_up), tonumber(v.limit_down))
617                         else
618                                 leasecount = leasecount + 1
619
620                                 -- only count leases_online for connected clients
621                                 if arpcache[v.mac] then
622                                         leases_online = leases_online + 1
623                                 end
624
625                                 -- Rewrite state
626                                 uci:section("luci_splash_leases", "lease", convert_mac_to_secname(v.mac), {             
627                                         mac    = v.mac,
628                                         ipaddr = v.ipaddr,
629                                         device = v.device,
630                                         limit_up = limit_up,
631                                         limit_down = limit_down,
632                                         start  = v.start
633                                 })
634                         end
635                 end
636         end
637         
638         -- Whitelist, Blacklist
639         for _, s in utl.spairs(blackwhitelist,
640                 function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
641         ) do
642                 if (s[".type"] == "whitelist") then
643                         whitelist_total = whitelist_total + 1
644                         if s.mac then
645                                 local mac = s.mac:lower()
646                                 if arpcache[mac] then
647                                         whitelist_online = whitelist_online + 1
648                                 end
649                         end
650                 end
651                 if (s[".type"] == "blacklist") then
652                         blacklist_total = blacklist_total + 1
653                         if s.mac then
654                                 local mac = s.mac:lower()
655                                 if arpcache[mac] then
656                                         blacklist_online = blacklist_online + 1
657                                 end
658                         end
659                 end
660         end
661
662         -- ToDo:
663         -- include a new field "leases_online" in stats to differ between active clients and leases:
664         -- update_stats(leasecount, leases_online, whitelist_online, whitelist_total, blacklist_online, blacklist_total) later: 
665         update_stats(leases_online, whitelist_online, whitelist_total, blacklist_online, blacklist_total)
666
667         uci:save("luci_splash_leases")
668
669         -- Get the mac addresses of current leases
670         local macs = get_known_macs()
671
672         ipt:resync()
673
674         ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
675                 function(r) return not macs[r.options[2]:lower()] end)
676         ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
677                 function(r) return not macs[r.options[2]:lower()] end)
678         ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
679                 function(r) return not macs[r.options[2]:lower()] end)
680         ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"/*", "MARK", "set"}},
681                 function(r) return not macs[r.options[2]:lower()] end)
682
683
684         if has_ipv6 then
685                 ipt6_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
686                         function(r) return not macs[r.options[2]:lower()] end)
687                 ipt6_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
688                         function(r) return not macs[r.options[2]:lower()] end)
689         end
690
691         unlock()
692 end
693
694 -- Show client info
695 function list()
696         local arpcache = get_arpcache()
697         -- Find traffic usage
698         local function traffic(lease)
699                 local traffic_in  = 0
700                 local traffic_out = 0
701
702                 local rin  = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
703                 local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
704
705                 if rin  and #rin  > 0 then traffic_in  = math.floor( rin[1].bytes / 1024) end
706                 if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
707
708                 return traffic_in, traffic_out
709         end
710
711         -- Print listings
712         local leases = uci:get_all("luci_splash_leases")
713         local blackwhitelist = uci:get_all("luci_splash")
714
715         print(string.format(
716                 "%-17s  %-15s  %-9s  %-4s  %-7s  %20s",
717                 "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
718         ))
719
720         -- Leases
721         for _, s in pairs(leases) do
722                 if s[".type"] == "lease" and s.mac then
723                         local ti, to = traffic(s)
724                         local mac = s.mac:lower()
725                         local arp = arpcache[mac]
726                         print(string.format(
727                                 "%-17s  %-15s  %-9s  %3dm  %-7s  %7dKB  %7dKB",
728                                 mac, s.ipaddr, "leased",
729                                 math.floor(( os.time() - tonumber(s.start) ) / 60),
730                                 arp and arp[1] or "?", ti, to
731                         ))
732                 end
733         end
734
735         -- Whitelist, Blacklist
736         for _, s in utl.spairs(blackwhitelist,
737                 function(a,b) return blackwhitelist[a][".type"] > blackwhitelist[b][".type"] end
738         ) do
739                 if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
740                         local mac = s.mac:lower()
741                         local arp = arpcache[mac]
742                         print(string.format(
743                                 "%-17s  %-15s  %-9s  %4s  %-7s  %9s  %9s",
744                                 mac, arp and arp[2] or "?", s[".type"],
745                                 "- ", arp and arp[1] or "?", "-", "-"
746                         ))
747                 end
748         end
749 end
750
751 main(arg)