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