luci-app-firewall: don't allow configuring src_mac for snat rules
[project/luci.git] / applications / luci-firewall / luasrc / model / cbi / firewall / rules.lua
1 --[[
2 LuCI - Lua Configuration Interface
3
4 Copyright 2008 Steven Barth <steven@midlink.org>
5 Copyright 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
6
7 Licensed under the Apache License, Version 2.0 (the "License");
8 you may not use this file except in compliance with the License.
9 You may obtain a copy of the License at
10
11         http://www.apache.org/licenses/LICENSE-2.0
12
13 ]]--
14
15 local ds = require "luci.dispatcher"
16 local ft = require "luci.tools.firewall"
17
18 m = Map("firewall",
19         translate("Firewall - Traffic Rules"),
20         translate("Traffic rules define policies for packets traveling between \
21                 different zones, for example to reject traffic between certain hosts \
22                 or to open WAN ports on the router."))
23
24 --
25 -- Rules
26 --
27
28 s = m:section(TypedSection, "rule", translate("Traffic Rules"))
29 s.addremove = true
30 s.anonymous = true
31 s.sortable  = true
32 s.template = "cbi/tblsection"
33 s.extedit   = ds.build_url("admin/network/firewall/rules/%s")
34 s.defaults.target = "ACCEPT"
35 s.template_addremove = "firewall/cbi_addrule"
36
37
38 function s.create(self, section)
39         created = TypedSection.create(self, section)
40 end
41
42 function s.parse(self, ...)
43         TypedSection.parse(self, ...)
44
45         local i_n = m:formvalue("_newopen.name")
46         local i_p = m:formvalue("_newopen.proto")
47         local i_e = m:formvalue("_newopen.extport")
48         local i_x = m:formvalue("_newopen.submit")
49
50         local f_n = m:formvalue("_newfwd.name")
51         local f_s = m:formvalue("_newfwd.src")
52         local f_d = m:formvalue("_newfwd.dest")
53         local f_x = m:formvalue("_newfwd.submit")
54
55         if i_x then
56                 created = TypedSection.create(self, section)
57
58                 self.map:set(created, "target",    "ACCEPT")
59                 self.map:set(created, "src",       "wan")
60                 self.map:set(created, "proto",     (i_p ~= "other") and i_p or "all")
61                 self.map:set(created, "dest_port", i_e)
62                 self.map:set(created, "name",      i_n)
63
64                 if i_p ~= "other" and i_e and #i_e > 0 then
65                         created = nil
66                 end
67
68         elseif f_x then
69                 created = TypedSection.create(self, section)
70
71                 self.map:set(created, "target", "ACCEPT")
72                 self.map:set(created, "src",    f_s)
73                 self.map:set(created, "dest",   f_d)
74                 self.map:set(created, "name",   f_n)
75         end
76
77         if created then
78                 m.uci:save("firewall")
79                 luci.http.redirect(ds.build_url(
80                         "admin/network/firewall/rules", created
81                 ))
82         end
83 end
84
85 ft.opt_name(s, DummyValue, translate("Name"))
86
87 local function rule_proto_txt(self, s)
88         local f = self.map:get(s, "family")
89         local p = ft.fmt_proto(self.map:get(s, "proto"),
90                                self.map:get(s, "icmp_type")) or "TCP+UDP"
91
92         if f and f:match("4") then
93                 return "%s-%s" %{ translate("IPv4"), p }
94         elseif f and f:match("6") then
95                 return "%s-%s" %{ translate("IPv6"), p }
96         else
97                 return "%s %s" %{ translate("Any"), p }
98         end
99 end
100
101 local function rule_src_txt(self, s)
102         local z = ft.fmt_zone(self.map:get(s, "src"), translate("any zone"))
103         local a = ft.fmt_ip(self.map:get(s, "src_ip"), translate("any host"))
104         local p = ft.fmt_port(self.map:get(s, "src_port"))
105         local m = ft.fmt_mac(self.map:get(s, "src_mac"))
106
107         if p and m then
108                 return translatef("From %s in %s with source %s and %s", a, z, p, m)
109         elseif p or m then
110                 return translatef("From %s in %s with source %s", a, z, p or m)
111         else
112                 return translatef("From %s in %s", a, z)
113         end
114 end
115
116 local function rule_dest_txt(self, s)
117         local z = ft.fmt_zone(self.map:get(s, "dest"))
118         local p = ft.fmt_port(self.map:get(s, "dest_port"))
119
120         -- Forward
121         if z then
122                 local a = ft.fmt_ip(self.map:get(s, "dest_ip"), translate("any host"))
123                 if p then
124                         return translatef("To %s, %s in %s", a, p, z)
125                 else
126                         return translatef("To %s in %s", a, z)
127                 end
128
129         -- Input
130         else
131                 local a = ft.fmt_ip(self.map:get(s, "dest_ip"),
132                         translate("any router IP"))
133
134                 if p then
135                         return translatef("To %s at %s on <var>this device</var>", a, p)
136                 else
137                         return translatef("To %s on <var>this device</var>", a)
138                 end
139         end
140 end
141
142 local function snat_dest_txt(self, s)
143         local z = ft.fmt_zone(self.map:get(s, "dest"), translate("any zone"))
144         local a = ft.fmt_ip(self.map:get(s, "dest_ip"), translate("any host"))
145         local p = ft.fmt_port(self.map:get(s, "dest_port")) or
146                 ft.fmt_port(self.map:get(s, "src_dport"))
147
148         if p then
149                 return translatef("To %s, %s in %s", a, p, z)
150         else
151                 return translatef("To %s in %s", a, z)
152         end
153 end
154
155
156 match = s:option(DummyValue, "match", translate("Match"))
157 match.rawhtml = true
158 match.width   = "70%"
159 function match.cfgvalue(self, s)
160         return "<small>%s<br />%s<br />%s</small>" % {
161                 rule_proto_txt(self, s),
162                 rule_src_txt(self, s),
163                 rule_dest_txt(self, s)
164         }
165 end
166
167 target = s:option(DummyValue, "target", translate("Action"))
168 target.rawhtml = true
169 target.width   = "20%"
170 function target.cfgvalue(self, s)
171         local t = ft.fmt_target(self.map:get(s, "target"), self.map:get(s, "dest"))
172         local l = ft.fmt_limit(self.map:get(s, "limit"),
173                 self.map:get(s, "limit_burst"))
174
175         if l then
176                 return translatef("<var>%s</var> and limit to %s", t, l)
177         else
178                 return "<var>%s</var>" % t
179         end
180 end
181
182 ft.opt_enabled(s, Flag, translate("Enable")).width = "1%"
183
184
185 --
186 -- SNAT
187 --
188
189 s = m:section(TypedSection, "redirect",
190         translate("Source NAT"),
191         translate("Source NAT is a specific form of masquerading which allows \
192                 fine grained control over the source IP used for outgoing traffic, \
193                 for example to map multiple WAN addresses to internal subnets."))
194 s.template  = "cbi/tblsection"
195 s.addremove = true
196 s.anonymous = true
197 s.sortable  = true
198 s.extedit   = ds.build_url("admin/network/firewall/rules/%s")
199 s.template_addremove = "firewall/cbi_addsnat"
200
201 function s.create(self, section)
202         created = TypedSection.create(self, section)
203 end
204
205 function s.parse(self, ...)
206         TypedSection.parse(self, ...)
207
208         local n = m:formvalue("_newsnat.name")
209         local s = m:formvalue("_newsnat.src")
210         local d = m:formvalue("_newsnat.dest")
211         local a = m:formvalue("_newsnat.dip")
212         local p = m:formvalue("_newsnat.dport")
213         local x = m:formvalue("_newsnat.submit")
214
215         if x and a and #a > 0 then
216                 created = TypedSection.create(self, section)
217
218                 self.map:set(created, "target",    "SNAT")
219                 self.map:set(created, "src",       s)
220                 self.map:set(created, "dest",      d)
221                 self.map:set(created, "proto",     "all")
222                 self.map:set(created, "src_dip",   a)
223                 self.map:set(created, "src_dport", p)
224                 self.map:set(created, "name",      n)
225         end
226
227         if created then
228                 m.uci:save("firewall")
229                 luci.http.redirect(ds.build_url(
230                         "admin/network/firewall/rules", created
231                 ))
232         end
233 end
234
235 function s.filter(self, sid)
236         return (self.map:get(sid, "target") == "SNAT")
237 end
238
239 ft.opt_name(s, DummyValue, translate("Name"))
240
241 match = s:option(DummyValue, "match", translate("Match"))
242 match.rawhtml = true
243 match.width   = "70%"
244 function match.cfgvalue(self, s)
245         return "<small>%s<br />%s<br />%s</small>" % {
246                 rule_proto_txt(self, s),
247                 rule_src_txt(self, s),
248                 snat_dest_txt(self, s)
249         }
250 end
251
252 snat = s:option(DummyValue, "via", translate("Action"))
253 snat.rawhtml = true
254 snat.width   = "20%"
255 function snat.cfgvalue(self, s)
256         local a = ft.fmt_ip(self.map:get(s, "src_dip"))
257         local p = ft.fmt_port(self.map:get(s, "src_dport"))
258
259         if a and p then
260                 return translatef("Rewrite to source %s, %s", a, p)
261         else
262                 return translatef("Rewrite to source %s", a or p)
263         end
264 end
265
266 ft.opt_enabled(s, Flag, translate("Enable")).width = "1%"
267
268
269 return m