applications/luci-splash: when more than two clients where leased and another one...
[project/luci.git] / applications / luci-splash / root / usr / sbin / luci-splash
1 #!/usr/bin/lua
2
3 require("luci.util")
4 require("luci.model.uci")
5 require("luci.sys")
6 require("luci.sys.iptparser")
7
8 -- Init state session
9 local uci = luci.model.uci.cursor_state()
10 local ipt = luci.sys.iptparser.IptParser()
11 local net = luci.sys.net
12
13 local limit_up = 0
14 local limit_down = 0
15
16 function lock()
17         os.execute("lock /var/run/luci_splash.lock")
18 end
19
20 function unlock()
21         os.execute("lock -u /var/run/luci_splash.lock")
22 end
23
24 function main(argv)
25         local cmd = table.remove(argv, 1)
26         local arg = argv[1]
27
28         limit_up = tonumber(uci:get("luci_splash", "general", "limit_up")) or 0
29         limit_down = tonumber(uci:get("luci_splash", "general", "limit_down")) or 0
30
31         if ( cmd == "lease" or cmd == "add-rules" or cmd == "remove" or
32              cmd == "whitelist" or cmd == "blacklist" or cmd == "status" ) and #argv > 0
33         then
34                 lock()
35
36                 local arp_cache      = net.arptable()
37                 local leased_macs    = get_known_macs("lease")
38                 local blacklist_macs = get_known_macs("blacklist")
39                 local whitelist_macs = get_known_macs("whitelist")
40
41                 for i, adr in ipairs(argv) do
42                         local mac = nil
43                         if adr:find(":") then
44                                 mac = adr:lower()
45                         else
46                                 for _, e in ipairs(arp_cache) do
47                                         if e["IP address"] == adr then
48                                                 mac = e["HW address"]:lower()
49                                                 break
50                                         end
51                                 end
52                         end
53
54                         if mac and cmd == "add-rules" then
55                                 if leased_macs[mac] then
56                                         add_lease(mac, arp_cache, true)
57                                 elseif blacklist_macs[mac] then
58                                         add_blacklist_rule(mac)
59                                 elseif whitelist_macs[mac] then
60                                         add_whitelist_rule(mac)
61                                 end
62                         elseif mac and cmd == "status" then
63                                 print(leased_macs[mac] and "lease"
64                                         or whitelist_macs[mac] and "whitelist"
65                                         or blacklist_macs[mac] and "blacklist"
66                                         or "new")
67                         elseif mac and ( cmd == "whitelist" or cmd == "blacklist" or cmd == "lease" ) then
68                                 if cmd ~= "lease" and leased_macs[mac] then
69                                         print("Removing %s from leases" % mac)
70                                         remove_lease(mac)
71                                         leased_macs[mac] = nil
72                                 end
73
74                                 if cmd ~= "whitelist" and whitelist_macs[mac] then
75                                         print("Removing %s from whitelist" % mac)
76                                         remove_whitelist(mac)
77                                         whitelist_macs[mac] = nil                                       
78                                 end
79
80                                 if cmd ~= "blacklist" and blacklist_macs[mac] then
81                                         print("Removing %s from blacklist" % mac)
82                                         remove_blacklist(mac)
83                                         blacklist_macs[mac] = nil
84                                 end
85
86                                 if cmd == "lease" and not leased_macs[mac] then
87                                         print("Adding %s to leases" % mac)
88                                         add_lease(mac)
89                                         leased_macs[mac] = true
90                                 elseif cmd == "whitelist" and not whitelist_macs[mac] then
91                                         print("Adding %s to whitelist" % mac)
92                                         add_whitelist(mac)
93                                         whitelist_macs[mac] = true
94                                 elseif cmd == "blacklist" and not blacklist_macs[mac] then
95                                         print("Adding %s to blacklist" % mac)
96                                         add_blacklist(mac)
97                                         blacklist_macs[mac] = true
98                                 else
99                                         print("The mac %s is already %sed" %{ mac, cmd })
100                                 end
101                         elseif mac and cmd == "remove" then
102                                 if leased_macs[mac] then
103                                         print("Removing %s from leases" % mac)
104                                         remove_lease(mac)
105                                         leased_macs[mac] = nil
106                                 elseif whitelist_macs[mac] then
107                                         print("Removing %s from whitelist" % mac)
108                                         remove_whitelist(mac)
109                                         whitelist_macs[mac] = nil                                       
110                                 elseif blacklist_macs[mac] then
111                                         print("Removing %s from blacklist" % mac)
112                                         remove_blacklist(mac)
113                                         blacklist_macs[mac] = nil
114                                 else
115                                         print("The mac %s is not known" % mac)
116                                 end
117                         else
118                                 print("Can not find mac for ip %s" % argv[i])
119                         end
120                 end
121
122                 unlock()
123                 os.exit(0)      
124         elseif cmd == "sync" then
125                 sync()
126                 os.exit(0)
127         elseif cmd == "list" then
128                 list()
129                 os.exit(0)
130         else
131                 print("Usage:")
132                 print("\n  luci-splash list\n    List connected, black- and whitelisted clients")
133                 print("\n  luci-splash sync\n    Synchronize firewall rules and clear expired leases")
134                 print("\n  luci-splash lease <MAC-or-IP>\n    Create a lease for the given address")
135                 print("\n  luci-splash blacklist <MAC-or-IP>\n    Add given address to blacklist")
136                 print("\n  luci-splash whitelist <MAC-or-IP>\n    Add given address to whitelist")
137                 print("\n  luci-splash remove <MAC-or-IP>\n    Remove given address from the lease-, black- or whitelist")
138                 print("")
139
140                 os.exit(1)      
141         end
142 end
143
144 -- Get a list of known mac addresses
145 function get_known_macs(list)
146         local leased_macs = { }
147
148         if not list or list == "lease" then
149                 uci:foreach("luci_splash", "lease",
150                         function(s) leased_macs[s.mac:lower()] = true end)
151         end
152
153         if not list or list == "whitelist" then
154                 uci:foreach("luci_splash", "whitelist",
155                         function(s) leased_macs[s.mac:lower()] = true end)
156         end
157
158         if not list or list == "blacklist" then
159                 uci:foreach("luci_splash", "blacklist",
160                         function(s) leased_macs[s.mac:lower()] = true end)
161         end
162
163         return leased_macs
164 end
165
166
167 -- Get a list of known ip addresses
168 function get_known_ips(macs, arp)
169         local leased_ips = { }
170         if not macs then macs = get_known_macs() end
171         for _, e in ipairs(arp or net.arptable()) do
172                 if macs[e["HW address"]:lower()] then leased_ips[e["IP address"]] = true end
173         end
174         return leased_ips
175 end
176
177
178 -- Helper to delete iptables rules
179 function ipt_delete_all(args, comp, off)
180         off = off or { }
181         for i, r in ipairs(ipt:find(args)) do
182                 if comp == nil or comp(r) then
183                         off[r.table] = off[r.table] or { }
184                         off[r.table][r.chain] = off[r.table][r.chain] or 0
185
186                         os.execute("iptables -t %q -D %q %d 2>/dev/null"
187                                 %{ r.table, r.chain, r.index - off[r.table][r.chain] })
188
189                         off[r.table][r.chain] = off[r.table][r.chain] + 1
190                 end
191         end
192 end
193
194 -- Convert mac to uci-compatible section name
195 function convert_mac_to_secname(mac)
196         return string.gsub(mac, ":", "")
197 end
198
199 -- Add a lease to state and invoke add_rule
200 function add_lease(mac, arp, no_uci)
201         mac = mac:lower()
202
203         -- Get current ip address
204         local ipaddr
205         for _, entry in ipairs(arp or net.arptable()) do
206                 if entry["HW address"]:lower() == mac then
207                         ipaddr = entry["IP address"]
208                         break
209                 end
210         end
211
212         -- Add lease if there is an ip addr
213         if ipaddr then
214                 if not no_uci then
215                         uci:section("luci_splash", "lease", convert_mac_to_secname(mac), {
216                                 mac    = mac,
217                                 ipaddr = ipaddr,
218                                 start  = os.time()
219                         })
220                         uci:save("luci_splash")
221                 end
222                 add_lease_rule(mac, ipaddr)
223         else
224                 print("Found no active IP for %s, lease not added" % mac)
225         end
226 end
227
228
229 -- Remove a lease from state and invoke remove_rule
230 function remove_lease(mac)
231         mac = mac:lower()
232
233         uci:delete_all("luci_splash", "lease",
234                 function(s)
235                         if s.mac:lower() == mac then
236                                 remove_lease_rule(mac, s.ipaddr)
237                                 return true
238                         end
239                         return false
240                 end)
241                 
242         uci:save("luci_splash")
243 end
244
245
246 -- Add a whitelist entry
247 function add_whitelist(mac)
248         uci:section("luci_splash", "whitelist", convert_mac_to_secname(mac), { mac = mac })
249         uci:save("luci_splash")
250         uci:commit("luci_splash")
251         add_whitelist_rule(mac)
252 end
253
254
255 -- Add a blacklist entry
256 function add_blacklist(mac)
257         uci:section("luci_splash", "blacklist", convert_mac_to_secname(mac), { mac = mac })
258         uci:save("luci_splash")
259         uci:commit("luci_splash")
260         add_blacklist_rule(mac)
261 end
262
263
264 -- Remove a whitelist entry
265 function remove_whitelist(mac)
266         mac = mac:lower()
267         uci:delete_all("luci_splash", "whitelist",
268                 function(s) return not s.mac or s.mac:lower() == mac end)
269         uci:save("luci_splash")
270         uci:commit("luci_splash")
271         remove_lease_rule(mac)
272 end
273
274
275 -- Remove a blacklist entry
276 function remove_blacklist(mac)
277         mac = mac:lower()
278         uci:delete_all("luci_splash", "blacklist",
279                 function(s) return not s.mac or s.mac:lower() == mac end)
280         uci:save("luci_splash")
281         uci:commit("luci_splash")
282         remove_lease_rule(mac)
283 end
284
285
286 -- Add an iptables rule
287 function add_lease_rule(mac, ipaddr)
288         if limit_up > 0 and limit_down > 0 then
289                 os.execute("iptables -t mangle -I luci_splash_mark_out -m mac --mac-source %q -j MARK --set-mark 79" % mac)
290                 os.execute("iptables -t mangle -I luci_splash_mark_in -d %q -j MARK --set-mark 80" % ipaddr)
291         end
292
293         os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
294         os.execute("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
295 end
296
297
298 -- Remove lease, black- or whitelist rules
299 function remove_lease_rule(mac, ipaddr)
300         ipt:resync()
301
302         if ipaddr then
303                 ipt_delete_all({table="mangle", chain="luci_splash_mark_in",  destination=ipaddr})
304                 ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", mac:upper()}})
305         end
306
307         ipt_delete_all({table="filter", chain="luci_splash_filter",   options={"MAC", mac:upper()}})
308         ipt_delete_all({table="nat",    chain="luci_splash_leases",   options={"MAC", mac:upper()}})
309 end
310
311
312 -- Add whitelist rules
313 function add_whitelist_rule(mac)
314         os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j RETURN" % mac)
315         os.execute("iptables -t nat    -I luci_splash_leases -m mac --mac-source %q -j RETURN" % mac)
316 end
317
318
319 -- Add blacklist rules
320 function add_blacklist_rule(mac)
321         os.execute("iptables -t filter -I luci_splash_filter -m mac --mac-source %q -j DROP" % mac)
322 end
323
324
325 -- Synchronise leases, remove abandoned rules
326 function sync()
327         lock()
328
329         local time = os.time()
330
331         -- Current leases in state files
332         local leases = uci:get_all("luci_splash")
333         
334         -- Convert leasetime to seconds
335         local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime")) * 3600
336         
337         -- Clean state file
338         uci:load("luci_splash")
339         uci:revert("luci_splash")
340         
341         -- For all leases
342         for k, v in pairs(leases) do
343                 if v[".type"] == "lease" then
344                         if os.difftime(time, tonumber(v.start)) > leasetime then
345                                 -- Remove expired
346                                 remove_lease_rule(v.mac, v.ipaddr)
347                         else
348                                 -- Rewrite state
349                                 uci:section("luci_splash", "lease", convert_mac_to_secname(v.mac), {            
350                                         mac    = v.mac,
351                                         ipaddr = v.ipaddr,
352                                         start  = v.start
353                                 })
354                         end
355                 end
356         end
357
358         uci:save("luci_splash")
359
360         -- Get current IPs and MAC addresses
361         local macs = get_known_macs()
362         local ips  = get_known_ips(macs)
363
364         ipt:resync()
365
366         ipt_delete_all({table="filter", chain="luci_splash_filter", options={"MAC"}},
367                 function(r) return not macs[r.options[2]:lower()] end)
368
369         ipt_delete_all({table="nat", chain="luci_splash_leases", options={"MAC"}},
370                 function(r) return not macs[r.options[2]:lower()] end)
371
372         ipt_delete_all({table="mangle", chain="luci_splash_mark_out", options={"MAC", "MARK", "set"}},
373                 function(r) return not macs[r.options[2]:lower()] end)
374
375         ipt_delete_all({table="mangle", chain="luci_splash_mark_in", options={"MARK", "set"}},
376                 function(r) return not ips[r.destination] end)
377
378         unlock()
379 end
380
381 -- Show client info
382 function list()
383         -- Get current arp cache
384         local arpcache = { }
385         for _, entry in ipairs(net.arptable()) do
386                 arpcache[entry["HW address"]:lower()] = { entry["Device"]:lower(), entry["IP address"]:lower() }
387         end
388
389         -- Find traffic usage
390         local function traffic(lease)
391                 local traffic_in  = 0
392                 local traffic_out = 0
393
394                 local rin  = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=lease.ipaddr})
395                 local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", lease.mac:upper()}})
396
397                 if rin  and #rin  > 0 then traffic_in  = math.floor( rin[1].bytes / 1024) end
398                 if rout and #rout > 0 then traffic_out = math.floor(rout[1].bytes / 1024) end
399
400                 return traffic_in, traffic_out
401         end
402
403         -- Print listings
404         local leases = uci:get_all("luci_splash")
405
406         print(string.format(
407                 "%-17s  %-15s  %-9s  %-4s  %-7s  %20s",
408                 "MAC", "IP", "State", "Dur.", "Intf.", "Traffic down/up"
409         ))
410
411         -- Leases
412         for _, s in pairs(leases) do
413                 if s[".type"] == "lease" and s.mac then
414                         local ti, to = traffic(s)
415                         local mac = s.mac:lower()
416                         local arp = arpcache[mac]
417                         print(string.format(
418                                 "%-17s  %-15s  %-9s  %3dm  %-7s  %7dKB  %7dKB",
419                                 mac, s.ipaddr, "leased",
420                                 math.floor(( os.time() - tonumber(s.start) ) / 60),
421                                 arp and arp[1] or "?", ti, to
422                         ))
423                 end
424         end
425
426         -- Whitelist, Blacklist
427         for _, s in luci.util.spairs(leases,
428                 function(a,b) return leases[a][".type"] > leases[b][".type"] end
429         ) do
430                 if (s[".type"] == "whitelist" or s[".type"] == "blacklist") and s.mac then
431                         local mac = s.mac:lower()
432                         local arp = arpcache[mac]
433                         print(string.format(
434                                 "%-17s  %-15s  %-9s  %4s  %-7s  %9s  %9s",
435                                 mac, arp and arp[2] or "?", s[".type"],
436                                 "- ", arp and arp[1] or "?", "-", "-"
437                         ))
438                 end
439         end
440 end
441
442 main(arg)