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