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