1baa2ce2323e3fa8bfebc0836c96f32c66236616
[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