libs/core: fix bug in 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 _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 add_zone(self, n)
68         if _valid_id(n) and not self:get_zone(n) then
69                 local z = uci_r:section("firewall", "zone", nil, {
70                         name    = n,
71                         network = " ",
72                         input   = defaults:input()   or "DROP",
73                         forward = defaults:forward() or "DROP",
74                         output  = defaults:output()  or "DROP"
75                 })
76
77                 return z and zone(z)
78         end
79 end
80
81 function get_zone(self, n)
82         if uci_r:get("firewall", n) == "zone" then
83                 return zone(n)
84         else
85                 local z
86                 uci_r:foreach("firewall", "zone",
87                         function(s)
88                                 if n and s.name == n then
89                                         z = s['.name']
90                                         return false
91                                 end
92                         end)
93                 return z and zone(z)
94         end
95 end
96
97 function get_zones(self)
98         local zones = { }
99         local znl = { }
100
101         uci_r:foreach("firewall", "zone",
102                 function(s)
103                         if s.name then
104                                 znl[s.name] = zone(s['.name'])
105                         end
106                 end)
107
108         local z
109         for z in utl.kspairs(znl) do
110                 zones[#zones+1] = znl[z]
111         end
112
113         return zones
114 end
115
116 function get_zone_by_network(self, net)
117         local z
118
119         uci_r:foreach("firewall", "zone",
120                 function(s)
121                         if s.name and net then
122                                 local n
123                                 for n in utl.imatch(s.network or s.name) do
124                                         if n == net then
125                                                 z = s['.name']
126                                                 return false
127                                         end
128                                 end
129                         end
130                 end)
131
132         return z and zone(z)
133 end
134
135 function del_zone(self, n)
136         local r = false
137
138         if uci_r:get("firewall", n) == "zone" then
139                 local z = uci_r:get("firewall", n, "name")
140                 r = uci_r:delete("firwall", n)
141                 n = z
142         else
143                 uci_r:foreach("firewall", "zone",
144                         function(s)
145                                 if n and s.name == n then
146                                         r = uci_r:delete("firewall", s['.name'])
147                                         return false
148                                 end
149                         end)
150         end
151
152         if r then
153                 uci_r:foreach("firewall", "rule",
154                         function(s)
155                                 if s.src == n or s.dest == n then
156                                         uci_r:delete("firewall", s['.name'])
157                                 end
158                         end)
159
160                 uci_r:foreach("firewall", "redirect",
161                         function(s)
162                                 if s.src == n then
163                                         uci_r:delete("firewall", s['.name'])
164                                 end
165                         end)
166
167                 uci_r:foreach("firewall", "forwarding",
168                         function(s)
169                                 if s.src == n then
170                                         uci_r:delete("firewall", s['.name'])
171                                 end
172                         end)
173         end
174
175         return r
176 end
177
178 function rename_zone(self, old, new)
179         local r = false
180
181         if _valid_id(new) and not self:get_zone(new) then
182                 uci_r:foreach("firewall", "zone",
183                         function(s)
184                                 if old and s.name == old then
185                                         if not s.network then
186                                                 uci_r:set("firewall", s['.name'], "network", old)
187                                         end
188                                         uci_r:set("firewall", s['.name'], "name", new)
189                                         r = true
190                                         return false
191                                 end
192                         end)
193
194                 if r then
195                         uci_r:foreach("firewall", "rule",
196                                 function(s)
197                                         if s.src == old then
198                                                 uci_r:set("firewall", s['.name'], "src", new)
199                                         end
200                                         if s.dest == old then
201                                                 uci_r:set("firewall", s['.name'], "dest", new)
202                                         end
203                                 end)
204
205                         uci_r:foreach("firewall", "redirect",
206                                 function(s)
207                                         if s.src == old then
208                                                 uci_r:set("firewall", s['.name'], "src", new)
209                                         end
210                                         if s.dest == old then
211                                                 uci_r:set("firewall", s['.name'], "dest", new)
212                                         end
213                                 end)
214
215                         uci_r:foreach("firewall", "forwarding",
216                                 function(s)
217                                         if s.src == old then
218                                                 uci_r:set("firewall", s['.name'], "src", new)
219                                         end
220                                         if s.dest == old then
221                                                 uci_r:set("firewall", s['.name'], "dest", new)
222                                         end
223                                 end)
224                 end
225         end
226
227         return r
228 end
229
230 function del_network(self, net)
231         local z
232         if net then
233                 for _, z in ipairs(self:get_zones()) do
234                         z:del_network(net)
235                 end
236         end
237 end
238
239
240 defaults = utl.class()
241 function defaults.__init__(self)
242         uci_r:foreach("firewall", "defaults",
243                 function(s)
244                         self.sid  = s['.name']
245                         return false
246                 end)
247
248         self.sid = self.sid or uci_r:section("firewall", "defaults", nil, { })
249 end
250
251 function defaults.get(self, opt)
252         return _get("firewall", self.sid, opt)
253 end
254
255 function defaults.set(self, opt, val)
256         return _set("firewall", self.sid, opt, val)
257 end
258
259 function defaults.syn_flood(self)
260         return (self:get("syn_flood") == "1")
261 end
262
263 function defaults.drop_invalid(self)
264         return (self:get("drop_invalid") == "1")
265 end
266
267 function defaults.input(self)
268         return self:get("input") or "DROP"
269 end
270
271 function defaults.forward(self)
272         return self:get("forward") or "DROP"
273 end
274
275 function defaults.output(self)
276         return self:get("output") or "DROP"
277 end
278
279
280 zone = utl.class()
281 function zone.__init__(self, z)
282         if uci_r:get("firewall", z) == "zone" then
283                 self.sid  = z
284                 self.data = uci_r:get_all("firewall", z)
285         else
286                 uci_r:foreach("firewall", "zone",
287                         function(s)
288                                 if s.name == z then
289                                         self.sid  = s['.name']
290                                         self.data = s
291                                         return false
292                                 end
293                         end)
294         end
295 end
296
297 function zone.get(self, opt)
298         return _get("firewall", self.sid, opt)
299 end
300
301 function zone.set(self, opt, val)
302         return _set("firewall", self.sid, opt, val)
303 end
304
305 function zone.masq(self)
306         return (self:get("masq") == "1")
307 end
308
309 function zone.name(self)
310         return self:get("name")
311 end
312
313 function zone.network(self)
314         return self:get("network")
315 end
316
317 function zone.input(self)
318         return self:get("input") or "DROP"
319 end
320
321 function zone.forward(self)
322         return self:get("forward") or "DROP"
323 end
324
325 function zone.output(self)
326         return self:get("output") or "DROP"
327 end
328
329 function zone.add_network(self, net)
330         if uci_r:get("network", net) == "interface" then
331                 local nets = { }
332
333                 local n
334                 for n in utl.imatch(self:get("network") or self:get("name")) do
335                         if n ~= net then
336                                 nets[#nets+1] = n
337                         end
338                 end
339
340                 nets[#nets+1] = net
341
342                 if #nets > 0 then
343                         self:set("network", table.concat(nets, " "))
344                 else
345                         self:set("network", " ")
346                 end
347         end
348 end
349
350 function zone.del_network(self, net)
351         local nets = { }
352
353         local n
354         for n in utl.imatch(self:get("network") or self:get("name")) do
355                 if n ~= net then
356                         nets[#nets+1] = n
357                 end
358         end
359
360         if #nets > 0 then
361                 self:set("network", table.concat(nets, " "))
362         else
363                 self:set("network", " ")
364         end
365 end
366
367 function zone.get_networks(self)
368         local nets = { }
369
370         local n
371         for n in utl.imatch(self:get("network") or self:get("name")) do
372                 nets[#nets+1] = n
373         end
374
375         return nets
376 end
377
378 function zone.get_forwardings_by(self, what)
379         local name = self:name()
380         local forwards = { }
381
382         uci_r:foreach("firewall", "forwarding",
383                 function(s)
384                         if s.src and s.dest and s[what] == name then
385                                 forwards[#forwards+1] = forwarding(s['.name'])
386                         end
387                 end)
388
389         return forwards
390 end
391
392 function zone.add_forwarding_to(self, dest)
393         local exist, forward
394
395         for _, forward in ipairs(self:get_forwardings_by('src')) do
396                 if forward:dest() == dest then
397                         exist = true
398                         break
399                 end
400         end
401
402         if not exist and dest ~= self:name() then
403                 local s = uci_r:section("firewall", "forwarding", nil, {
404                         src     = self:name(),
405                         dest    = dest
406                 })
407
408                 return s and forwarding(s)
409         end
410 end
411
412 function zone.add_forwarding_from(self, src)
413         local exist, forward
414
415         for _, forward in ipairs(self:get_forwardings_by('dest')) do
416                 if forward:src() == src then
417                         exist = true
418                         break
419                 end
420         end
421
422         if not exist and src ~= self:name() then
423                 local s = uci_r:section("firewall", "forwarding", nil, {
424                         src     = src,
425                         dest    = self:name()
426                 })
427
428                 return s and forwarding(s)
429         end
430 end
431
432 function zone.del_forwardings_by(self, what)
433         local name = self:name()
434
435         uci_r:foreach("firewall", "forwarding",
436                 function(s)
437                         if s.src and s.dest and s[what] == name then
438                                 uci_r:delete("firewall", s['.name'])
439                         end
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(lmo.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         return zone(self:src())
502 end
503
504 function forwarding.dest_zone(self)
505         return zone(self:dest())
506 end
507
508
509 rule = utl.class()
510 function rule.__init__(self, f)
511         self.sid = f
512 end
513
514 function rule.get(self, opt)
515         return _get("firewall", self.sid, opt)
516 end
517
518 function rule.set(self, opt, val)
519         return _set("firewall", self.sid, opt, val)
520 end
521
522 function rule.src(self)
523         return uci_r:get("firewall", self.sid, "src")
524 end
525
526 function rule.dest(self)
527         return uci_r:get("firewall", self.sid, "dest")
528 end
529
530 function rule.src_zone(self)
531         return zone(self:src())
532 end
533
534 function rule.dest_zone(self)
535         return zone(self:dest())
536 end
537
538
539 redirect = utl.class()
540 function redirect.__init__(self, f)
541         self.sid = f
542 end
543
544 function redirect.get(self, opt)
545         return _get("firewall", self.sid, opt)
546 end
547
548 function redirect.set(self, opt, val)
549         return _set("firewall", self.sid, opt, val)
550 end
551
552 function redirect.src(self)
553         return uci_r:get("firewall", self.sid, "src")
554 end
555
556 function redirect.dest(self)
557         return uci_r:get("firewall", self.sid, "dest")
558 end
559
560 function redirect.src_zone(self)
561         return zone(self:src())
562 end
563
564 function redirect.dest_zone(self)
565         return zone(self:dest())
566 end