modules/base: work around wireless status changes
[project/luci.git] / modules / base / luasrc / model / firewall.lua
1 --[[
2 LuCI - Firewall model
3
4 Copyright 2009 Jo-Philipp Wich <xm@subsignal.org>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10         http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17
18 ]]--
19
20 local type, pairs, ipairs, table, luci, math
21         = type, pairs, ipairs, table, luci, math
22
23 local tpl = require "luci.template.parser"
24 local utl = require "luci.util"
25 local uci = require "luci.model.uci"
26
27 module "luci.model.firewall"
28
29
30 local uci_r, uci_s
31
32 function _valid_id(x)
33         return (x and #x > 0 and x:match("^[a-zA-Z0-9_]+$"))
34 end
35
36 function _get(c, s, o)
37         return uci_r:get(c, s, o)
38 end
39
40 function _set(c, s, o, v)
41         if v ~= nil then
42                 if type(v) == "boolean" then v = v and "1" or "0" end
43                 return uci_r:set(c, s, o, v)
44         else
45                 return uci_r:delete(c, s, o)
46         end
47 end
48
49
50 function init(cursor)
51         uci_r = cursor or uci_r or uci.cursor()
52         uci_s = uci_r:substate()
53
54         return _M
55 end
56
57 function save(self, ...)
58         uci_r:save(...)
59         uci_r:load(...)
60 end
61
62 function commit(self, ...)
63         uci_r:commit(...)
64         uci_r:load(...)
65 end
66
67 function get_defaults()
68         return defaults()
69 end
70
71 function new_zone(self)
72         local name = "newzone"
73         local count = 1
74
75         while self:get_zone(name) do
76                 count = count + 1
77                 name = "newzone%d" % count
78         end
79
80         return self:add_zone(name)
81 end
82
83 function add_zone(self, n)
84         if _valid_id(n) and not self:get_zone(n) then
85                 local d = defaults()
86                 local z = uci_r:section("firewall", "zone", nil, {
87                         name    = n,
88                         network = " ",
89                         input   = d:input()   or "DROP",
90                         forward = d:forward() or "DROP",
91                         output  = d:output()  or "DROP"
92                 })
93
94                 return z and zone(z)
95         end
96 end
97
98 function get_zone(self, n)
99         if uci_r:get("firewall", n) == "zone" then
100                 return zone(n)
101         else
102                 local z
103                 uci_r:foreach("firewall", "zone",
104                         function(s)
105                                 if n and s.name == n then
106                                         z = s['.name']
107                                         return false
108                                 end
109                         end)
110                 return z and zone(z)
111         end
112 end
113
114 function get_zones(self)
115         local zones = { }
116         local znl = { }
117
118         uci_r:foreach("firewall", "zone",
119                 function(s)
120                         if s.name then
121                                 znl[s.name] = zone(s['.name'])
122                         end
123                 end)
124
125         local z
126         for z in utl.kspairs(znl) do
127                 zones[#zones+1] = znl[z]
128         end
129
130         return zones
131 end
132
133 function get_zone_by_network(self, net)
134         local z
135
136         uci_r:foreach("firewall", "zone",
137                 function(s)
138                         if s.name and net then
139                                 local n
140                                 for n in utl.imatch(s.network or s.name) do
141                                         if n == net then
142                                                 z = s['.name']
143                                                 return false
144                                         end
145                                 end
146                         end
147                 end)
148
149         return z and zone(z)
150 end
151
152 function del_zone(self, n)
153         local r = false
154
155         if uci_r:get("firewall", n) == "zone" then
156                 local z = uci_r:get("firewall", n, "name")
157                 r = uci_r:delete("firewall", n)
158                 n = z
159         else
160                 uci_r:foreach("firewall", "zone",
161                         function(s)
162                                 if n and s.name == n then
163                                         r = uci_r:delete("firewall", s['.name'])
164                                         return false
165                                 end
166                         end)
167         end
168
169         if r then
170                 uci_r:foreach("firewall", "rule",
171                         function(s)
172                                 if s.src == n or s.dest == n then
173                                         uci_r:delete("firewall", s['.name'])
174                                 end
175                         end)
176
177                 uci_r:foreach("firewall", "redirect",
178                         function(s)
179                                 if s.src == n or s.dest == n then
180                                         uci_r:delete("firewall", s['.name'])
181                                 end
182                         end)
183
184                 uci_r:foreach("firewall", "forwarding",
185                         function(s)
186                                 if s.src == n or s.dest == n then
187                                         uci_r:delete("firewall", s['.name'])
188                                 end
189                         end)
190         end
191
192         return r
193 end
194
195 function rename_zone(self, old, new)
196         local r = false
197
198         if _valid_id(new) and not self:get_zone(new) then
199                 uci_r:foreach("firewall", "zone",
200                         function(s)
201                                 if old and s.name == old then
202                                         if not s.network then
203                                                 uci_r:set("firewall", s['.name'], "network", old)
204                                         end
205                                         uci_r:set("firewall", s['.name'], "name", new)
206                                         r = true
207                                         return false
208                                 end
209                         end)
210
211                 if r then
212                         uci_r:foreach("firewall", "rule",
213                                 function(s)
214                                         if s.src == old then
215                                                 uci_r:set("firewall", s['.name'], "src", new)
216                                         end
217                                         if s.dest == old then
218                                                 uci_r:set("firewall", s['.name'], "dest", new)
219                                         end
220                                 end)
221
222                         uci_r:foreach("firewall", "redirect",
223                                 function(s)
224                                         if s.src == old then
225                                                 uci_r:set("firewall", s['.name'], "src", new)
226                                         end
227                                         if s.dest == old then
228                                                 uci_r:set("firewall", s['.name'], "dest", new)
229                                         end
230                                 end)
231
232                         uci_r:foreach("firewall", "forwarding",
233                                 function(s)
234                                         if s.src == old then
235                                                 uci_r:set("firewall", s['.name'], "src", new)
236                                         end
237                                         if s.dest == old then
238                                                 uci_r:set("firewall", s['.name'], "dest", new)
239                                         end
240                                 end)
241                 end
242         end
243
244         return r
245 end
246
247 function del_network(self, net)
248         local z
249         if net then
250                 for _, z in ipairs(self:get_zones()) do
251                         z:del_network(net)
252                 end
253         end
254 end
255
256
257 defaults = utl.class()
258 function defaults.__init__(self)
259         uci_r:foreach("firewall", "defaults",
260                 function(s)
261                         self.sid  = s['.name']
262                         return false
263                 end)
264
265         self.sid = self.sid or uci_r:section("firewall", "defaults", nil, { })
266 end
267
268 function defaults.get(self, opt)
269         return _get("firewall", self.sid, opt)
270 end
271
272 function defaults.set(self, opt, val)
273         return _set("firewall", self.sid, opt, val)
274 end
275
276 function defaults.syn_flood(self)
277         return (self:get("syn_flood") == "1")
278 end
279
280 function defaults.drop_invalid(self)
281         return (self:get("drop_invalid") == "1")
282 end
283
284 function defaults.input(self)
285         return self:get("input") or "DROP"
286 end
287
288 function defaults.forward(self)
289         return self:get("forward") or "DROP"
290 end
291
292 function defaults.output(self)
293         return self:get("output") or "DROP"
294 end
295
296
297 zone = utl.class()
298 function zone.__init__(self, z)
299         if uci_r:get("firewall", z) == "zone" then
300                 self.sid  = z
301                 self.data = uci_r:get_all("firewall", z)
302         else
303                 uci_r:foreach("firewall", "zone",
304                         function(s)
305                                 if s.name == z then
306                                         self.sid  = s['.name']
307                                         self.data = s
308                                         return false
309                                 end
310                         end)
311         end
312 end
313
314 function zone.get(self, opt)
315         return _get("firewall", self.sid, opt)
316 end
317
318 function zone.set(self, opt, val)
319         return _set("firewall", self.sid, opt, val)
320 end
321
322 function zone.masq(self)
323         return (self:get("masq") == "1")
324 end
325
326 function zone.name(self)
327         return self:get("name")
328 end
329
330 function zone.network(self)
331         return self:get("network")
332 end
333
334 function zone.input(self)
335         return self:get("input") or defaults():input() or "DROP"
336 end
337
338 function zone.forward(self)
339         return self:get("forward") or defaults():forward() or "DROP"
340 end
341
342 function zone.output(self)
343         return self:get("output") or defaults():output() or "DROP"
344 end
345
346 function zone.add_network(self, net)
347         if uci_r:get("network", net) == "interface" then
348                 local nets = { }
349
350                 local n
351                 for n in utl.imatch(self:get("network") or self:get("name")) do
352                         if n ~= net then
353                                 nets[#nets+1] = n
354                         end
355                 end
356
357                 nets[#nets+1] = net
358
359                 _M:del_network(net)
360                 self:set("network", table.concat(nets, " "))
361         end
362 end
363
364 function zone.del_network(self, net)
365         local nets = { }
366
367         local n
368         for n in utl.imatch(self:get("network") or self:get("name")) do
369                 if n ~= net then
370                         nets[#nets+1] = n
371                 end
372         end
373
374         if #nets > 0 then
375                 self:set("network", table.concat(nets, " "))
376         else
377                 self:set("network", " ")
378         end
379 end
380
381 function zone.get_networks(self)
382         local nets = { }
383
384         local n
385         for n in utl.imatch(self:get("network") or self:get("name")) do
386                 nets[#nets+1] = n
387         end
388
389         return nets
390 end
391
392 function zone.clear_networks(self)
393         self:set("network", " ")
394 end
395
396 function zone.get_forwardings_by(self, what)
397         local name = self:name()
398         local forwards = { }
399
400         uci_r:foreach("firewall", "forwarding",
401                 function(s)
402                         if s.src and s.dest and s[what] == name then
403                                 forwards[#forwards+1] = forwarding(s['.name'])
404                         end
405                 end)
406
407         return forwards
408 end
409
410 function zone.add_forwarding_to(self, dest)
411         local exist, forward
412
413         for _, forward in ipairs(self:get_forwardings_by('src')) do
414                 if forward:dest() == dest then
415                         exist = true
416                         break
417                 end
418         end
419
420         if not exist and dest ~= self:name() and _valid_id(dest) then
421                 local s = uci_r:section("firewall", "forwarding", nil, {
422                         src     = self:name(),
423                         dest    = dest
424                 })
425
426                 return s and forwarding(s)
427         end
428 end
429
430 function zone.add_forwarding_from(self, src)
431         local exist, forward
432
433         for _, forward in ipairs(self:get_forwardings_by('dest')) do
434                 if forward:src() == src then
435                         exist = true
436                         break
437                 end
438         end
439
440         if not exist and src ~= self:name() and _valid_id(src) then
441                 local s = uci_r:section("firewall", "forwarding", nil, {
442                         src     = src,
443                         dest    = self:name()
444                 })
445
446                 return s and forwarding(s)
447         end
448 end
449
450 function zone.del_forwardings_by(self, what)
451         local name = self:name()
452
453         uci_r:delete_all("firewall", "forwarding",
454                 function(s)
455                         return (s.src and s.dest and s[what] == name)
456                 end)
457 end
458
459 function zone.add_redirect(self, options)
460         options = options or { }
461         options.src = self:name()
462
463         local s = uci_r:section("firewall", "redirect", nil, options)
464         return s and redirect(s)
465 end
466
467 function zone.add_rule(self, options)
468         options = options or { }
469         options.src = self:name()
470
471         local s = uci_r:section("firewall", "rule", nil, options)
472         return s and rule(s)
473 end
474
475 function zone.get_color(self)
476         if self and self:name() == "lan" then
477                 return "#90f090"
478         elseif self and self:name() == "wan" then
479                 return "#f09090"
480         elseif self then
481                 math.randomseed(tpl.hash(self:name()))
482
483                 local r   = math.random(128)
484                 local g   = math.random(128)
485                 local min = 0
486                 local max = 128
487
488                 if ( r + g ) < 128 then
489                         min = 128 - r - g
490                 else
491                         max = 255 - r - g
492                 end
493
494                 local b = min + math.floor( math.random() * ( max - min ) )
495
496                 return "#%02x%02x%02x" % { 0xFF - r, 0xFF - g, 0xFF - b }
497         else
498                 return "#eeeeee"
499         end
500 end
501
502
503 forwarding = utl.class()
504 function forwarding.__init__(self, f)
505         self.sid = f
506 end
507
508 function forwarding.src(self)
509         return uci_r:get("firewall", self.sid, "src")
510 end
511
512 function forwarding.dest(self)
513         return uci_r:get("firewall", self.sid, "dest")
514 end
515
516 function forwarding.src_zone(self)
517         return zone(self:src())
518 end
519
520 function forwarding.dest_zone(self)
521         return zone(self:dest())
522 end
523
524
525 rule = utl.class()
526 function rule.__init__(self, f)
527         self.sid = f
528 end
529
530 function rule.get(self, opt)
531         return _get("firewall", self.sid, opt)
532 end
533
534 function rule.set(self, opt, val)
535         return _set("firewall", self.sid, opt, val)
536 end
537
538 function rule.src(self)
539         return uci_r:get("firewall", self.sid, "src")
540 end
541
542 function rule.dest(self)
543         return uci_r:get("firewall", self.sid, "dest")
544 end
545
546 function rule.src_zone(self)
547         return zone(self:src())
548 end
549
550 function rule.dest_zone(self)
551         return zone(self:dest())
552 end
553
554
555 redirect = utl.class()
556 function redirect.__init__(self, f)
557         self.sid = f
558 end
559
560 function redirect.get(self, opt)
561         return _get("firewall", self.sid, opt)
562 end
563
564 function redirect.set(self, opt, val)
565         return _set("firewall", self.sid, opt, val)
566 end
567
568 function redirect.src(self)
569         return uci_r:get("firewall", self.sid, "src")
570 end
571
572 function redirect.dest(self)
573         return uci_r:get("firewall", self.sid, "dest")
574 end
575
576 function redirect.src_zone(self)
577         return zone(self:src())
578 end
579
580 function redirect.dest_zone(self)
581         return zone(self:dest())
582 end