diff options
Diffstat (limited to 'applications/luci-firewall/luasrc/model')
5 files changed, 380 insertions, 204 deletions
diff --git a/applications/luci-firewall/luasrc/model/cbi/luci_fw/redirect.lua b/applications/luci-firewall/luasrc/model/cbi/luci_fw/redirect.lua deleted file mode 100644 index da87015c86..0000000000 --- a/applications/luci-firewall/luasrc/model/cbi/luci_fw/redirect.lua +++ /dev/null @@ -1,52 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface - -Copyright 2008 Steven Barth <steven@midlink.org> - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -$Id$ -]]-- -require("luci.sys") -m = Map("firewall", translate("Traffic Redirection"), - translate("Traffic redirection allows you to change the " .. - "destination address of forwarded packets.")) - - -s = m:section(TypedSection, "redirect", "") -s.template = "cbi/tblsection" -s.addremove = true -s.anonymous = true -s.extedit = luci.dispatcher.build_url("admin", "network", "firewall", "redirect", "%s") - -name = s:option(Value, "_name", translate("Name"), translate("(optional)")) -name.size = 10 - -iface = s:option(ListValue, "src", translate("Zone")) -iface.default = "wan" -luci.model.uci.cursor():foreach("firewall", "zone", - function (section) - iface:value(section.name) - end) - -proto = s:option(ListValue, "proto", translate("Protocol")) -proto:value("tcp", "TCP") -proto:value("udp", "UDP") -proto:value("tcpudp", "TCP+UDP") - -dport = s:option(Value, "src_dport", translate("Source port")) -dport.size = 5 - -to = s:option(Value, "dest_ip", translate("Destination IP")) -for i, dataset in ipairs(luci.sys.net.arptable()) do - to:value(dataset["IP address"]) -end - -toport = s:option(Value, "dest_port", translate("Destination port")) -toport.size = 5 - -return m diff --git a/applications/luci-firewall/luasrc/model/cbi/luci_fw/rrule.lua b/applications/luci-firewall/luasrc/model/cbi/luci_fw/rrule.lua index 63e014444b..6332d8e8c1 100644 --- a/applications/luci-firewall/luasrc/model/cbi/luci_fw/rrule.lua +++ b/applications/luci-firewall/luasrc/model/cbi/luci_fw/rrule.lua @@ -2,6 +2,7 @@ LuCI - Lua Configuration Interface Copyright 2008 Steven Barth <steven@midlink.org> +Copyright 2010 Jo-Philipp Wich <xm@subsignal.org> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11,70 +12,130 @@ You may obtain a copy of the License at $Id$ ]]-- -require("luci.sys") + +local sys = require "luci.sys" +local dsp = require "luci.dispatcher" + arg[1] = arg[1] or "" m = Map("firewall", translate("Traffic Redirection"), translate("Traffic redirection allows you to change the " .. "destination address of forwarded packets.")) +m.redirect = dsp.build_url("admin", "network", "firewall") + +if not m.uci:get(arg[1]) == "redirect" then + luci.http.redirect(m.redirect) + return +end + +local has_v2 = nixio.fs.access("/lib/firewall/fw.sh") +local wan_zone = nil + +m.uci:foreach("firewall", "zone", + function(s) + local n = s.network or s.name + if n then + local i + for i in n:gmatch("%S+") do + if i == "wan" then + wan_zone = s.name + return false + end + end + end + end) s = m:section(NamedSection, arg[1], "redirect", "") s.anonymous = true s.addremove = false -back = s:option(DummyValue, "_overview", translate("Overview")) +s:tab("general", translate("General Settings")) +s:tab("advanced", translate("Advanced Settings")) + +back = s:taboption("general", DummyValue, "_overview", translate("Overview")) back.value = "" back.titleref = luci.dispatcher.build_url("admin", "network", "firewall", "redirect") -name = s:option(Value, "_name", translate("Name")) +name = s:taboption("general", Value, "_name", translate("Name")) name.rmempty = true name.size = 10 -iface = s:option(ListValue, "src", translate("Source zone")) -iface.default = "wan" -luci.model.uci.cursor():foreach("firewall", "zone", - function (section) - iface:value(section.name) - end) - -s:option(Value, "src_ip", translate("Source IP address")).optional = true -s:option(Value, "src_mac", translate("Source MAC-address")).optional = true +src = s:taboption("general", Value, "src", translate("Source zone")) +src.nocreate = true +src.default = "wan" +src.template = "cbi/firewall_zonelist" -sport = s:option(Value, "src_port", translate("Source port"), - translate("Match incoming traffic originating from the given " .. - "source port or port range on the client host")) -sport.optional = true -sport:depends("proto", "tcp") -sport:depends("proto", "udp") -sport:depends("proto", "tcpudp") - -proto = s:option(ListValue, "proto", translate("Protocol")) +proto = s:taboption("general", ListValue, "proto", translate("Protocol")) proto.optional = true -proto:value("") +proto:value("tcpudp", "TCP+UDP") proto:value("tcp", "TCP") proto:value("udp", "UDP") -proto:value("tcpudp", "TCP+UDP") -dport = s:option(Value, "src_dport", translate("External port"), +dport = s:taboption("general", Value, "src_dport", translate("External port"), translate("Match incoming traffic directed at the given " .. "destination port or port range on this host")) -dport.size = 5 +dport.datatype = "portrange" dport:depends("proto", "tcp") dport:depends("proto", "udp") dport:depends("proto", "tcpudp") -to = s:option(Value, "dest_ip", translate("Internal IP address"), +to = s:taboption("general", Value, "dest_ip", translate("Internal IP address"), translate("Redirect matched incoming traffic to the specified " .. "internal host")) +to.datatype = "ip4addr" for i, dataset in ipairs(luci.sys.net.arptable()) do to:value(dataset["IP address"]) end -toport = s:option(Value, "dest_port", translate("Internal port (optional)"), +toport = s:taboption("general", Value, "dest_port", translate("Internal port (optional)"), translate("Redirect matched incoming traffic to the given port on " .. "the internal host")) toport.optional = true toport.size = 5 + +target = s:taboption("advanced", ListValue, "target", translate("Redirection type")) +target:value("DNAT") +target:value("SNAT") + +dest = s:taboption("advanced", Value, "dest", translate("Destination zone")) +dest.nocreate = true +dest.default = "lan" +dest.template = "cbi/firewall_zonelist" + +src_dip = s:taboption("advanced", Value, "src_dip", + translate("Intended destination address"), + translate( + "For DNAT, match incoming traffic directed at the given destination ".. + "ip address. For SNAT rewrite the source address to the given address." + )) + +src_dip.optional = true +src_dip.datatype = "ip4addr" + +src_mac = s:taboption("advanced", Value, "src_mac", translate("Source MAC address")) +src_mac.optional = true +src_mac.datatype = "macaddr" + +src_ip = s:taboption("advanced", Value, "src_ip", translate("Source IP address")) +src_ip.optional = true +src_ip.datatype = "ip4addr" + +sport = s:taboption("advanced", Value, "src_port", translate("Source port"), + translate("Match incoming traffic originating from the given " .. + "source port or port range on the client host")) +sport.optional = true +sport.datatype = "portrange" +sport:depends("proto", "tcp") +sport:depends("proto", "udp") +sport:depends("proto", "tcpudp") + +reflection = s:taboption("advanced", Flag, "reflection", translate("Enable NAT Loopback")) +reflection.rmempty = true +reflection:depends({ target = "DNAT", src = wan_zone }) +reflection.cfgvalue = function(...) + return Flag.cfgvalue(...) or "1" +end + return m diff --git a/applications/luci-firewall/luasrc/model/cbi/luci_fw/traffic.lua b/applications/luci-firewall/luasrc/model/cbi/luci_fw/traffic.lua deleted file mode 100644 index 3bdc6db4c5..0000000000 --- a/applications/luci-firewall/luasrc/model/cbi/luci_fw/traffic.lua +++ /dev/null @@ -1,88 +0,0 @@ ---[[ -LuCI - Lua Configuration Interface - -Copyright 2008 Steven Barth <steven@midlink.org> -Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net> - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -$Id$ -]]-- - -m = Map("firewall", translate("Traffic Control")) -s = m:section(TypedSection, "forwarding", translate("Zone-to-Zone traffic"), - translate("Here you can specify which network traffic is allowed " .. - "to flow between network zones. Only new connections will " .. - "be matched. Packets belonging to already open " .. - "connections are automatically allowed to pass the " .. - "firewall. If you experience occasional connection " .. - "problems try enabling MSS Clamping otherwise disable it " .. - "for performance reasons.")) -s.template = "cbi/tblsection" -s.addremove = true -s.anonymous = true - -iface = s:option(ListValue, "src", translate("Source")) -oface = s:option(ListValue, "dest", translate("Destination")) - -luci.model.uci.cursor():foreach("firewall", "zone", - function (section) - iface:value(section.name) - oface:value(section.name) - end) - - - -s = m:section(TypedSection, "rule", translate("Rules")) -s.addremove = true -s.anonymous = true -s.template = "cbi/tblsection" -s.extedit = luci.dispatcher.build_url("admin", "network", "firewall", "rule", "%s") -s.defaults.target = "ACCEPT" - -local created = nil - -function s.create(self, section) - created = TypedSection.create(self, section) -end - -function s.parse(self, ...) - TypedSection.parse(self, ...) - if created then - m.uci:save("firewall") - luci.http.redirect(luci.dispatcher.build_url( - "admin", "network", "firewall", "rule", created - )) - end -end - -s:option(DummyValue, "_name", translate("Name")) -s:option(DummyValue, "proto", translate("Protocol")) - -src = s:option(DummyValue, "src", translate("Source")) -function src.cfgvalue(self, s) - return "%s:%s:%s" % { - self.map:get(s, "src") or "*", - self.map:get(s, "src_ip") or "0.0.0.0/0", - self.map:get(s, "src_port") or "*" - } -end - -dest = s:option(DummyValue, "dest", translate("Destination")) -function dest.cfgvalue(self, s) - return "%s:%s:%s" % { - self.map:get(s, "dest") or translate("Device"), - self.map:get(s, "dest_ip") or "0.0.0.0/0", - self.map:get(s, "dest_port") or "*" - } -end - - -s:option(DummyValue, "target", translate("Action")) - - -return m diff --git a/applications/luci-firewall/luasrc/model/cbi/luci_fw/trule.lua b/applications/luci-firewall/luasrc/model/cbi/luci_fw/trule.lua index 0ce41e38c7..7ee8fd8e54 100644 --- a/applications/luci-firewall/luasrc/model/cbi/luci_fw/trule.lua +++ b/applications/luci-firewall/luasrc/model/cbi/luci_fw/trule.lua @@ -2,6 +2,7 @@ LuCI - Lua Configuration Interface Copyright 2008 Steven Barth <steven@midlink.org> +Copyright 2010 Jo-Philipp Wich <xm@subsignal.org> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11,62 +12,121 @@ You may obtain a copy of the License at $Id$ ]]-- + +local has_v2 = nixio.fs.access("/lib/firewall/fw.sh") +local dsp = require "luci.dispatcher" + arg[1] = arg[1] or "" + m = Map("firewall", translate("Advanced Rules"), translate("Advanced rules let you customize the firewall to your " .. "needs. Only new connections will be matched. Packets " .. "belonging to already open connections are automatically " .. "allowed to pass the firewall.")) +m.redirect = dsp.build_url("admin", "network", "firewall") + +if not m.uci:get(arg[1]) == "rule" then + luci.http.redirect(m.redirect) + return +end + s = m:section(NamedSection, arg[1], "rule", "") s.anonymous = true s.addremove = false +s:tab("general", translate("General Settings")) +s:tab("advanced", translate("Advanced Options")) + back = s:option(DummyValue, "_overview", translate("Overview")) back.value = "" -back.titleref = luci.dispatcher.build_url("admin", "network", "firewall", "rule") +back.titleref = dsp.build_url("admin", "network", "firewall", "rule") -name = s:option(Value, "_name", translate("Name").." "..translate("(optional)")) +name = s:taboption("general", Value, "_name", translate("Name").." "..translate("(optional)")) name.rmempty = true -iface = s:option(ListValue, "src", translate("Source zone")) -iface.rmempty = true +src = s:taboption("general", Value, "src", translate("Source zone")) +src.nocreate = true +src.default = "wan" +src.template = "cbi/firewall_zonelist" -oface = s:option(ListValue, "dest", translate("Destination zone")) -oface:value("", translate("any")) -oface.rmempty = true +dest = s:taboption("advanced", Value, "dest", translate("Destination zone")) +dest.nocreate = true +dest.default = "lan" +dest.template = "cbi/firewall_zonelist" -luci.model.uci.cursor():foreach("firewall", "zone", - function (section) - iface:value(section.name) - oface:value(section.name) - end) - -proto = s:option(Value, "proto", translate("Protocol")) +proto = s:taboption("general", Value, "proto", translate("Protocol")) proto.optional = true -proto:value("") proto:value("all", translate("Any")) proto:value("tcpudp", "TCP+UDP") proto:value("tcp", "TCP") proto:value("udp", "UDP") proto:value("icmp", "ICMP") -s:option(Value, "src_ip", translate("Source address")).optional = true -s:option(Value, "dest_ip", translate("Destination address")).optional = true -s:option(Value, "src_mac", translate("Source MAC-address")).optional = true - -sport = s:option(Value, "src_port", translate("Source port")) +icmpt = s:taboption("general", Value, "icmp_type", translate("Match ICMP type")) +icmpt:depends("proto", "icmp") +icmpt:value("any") +icmpt:value("echo-reply") +icmpt:value("destination-unreachable") +icmpt:value("network-unreachable") +icmpt:value("host-unreachable") +icmpt:value("protocol-unreachable") +icmpt:value("port-unreachable") +icmpt:value("fragmentation-needed") +icmpt:value("source-route-failed") +icmpt:value("network-unknown") +icmpt:value("host-unknown") +icmpt:value("network-prohibited") +icmpt:value("host-prohibited") +icmpt:value("TOS-network-unreachable") +icmpt:value("TOS-host-unreachable") +icmpt:value("communication-prohibited") +icmpt:value("host-precedence-violation") +icmpt:value("precedence-cutoff") +icmpt:value("source-quench") +icmpt:value("redirect") +icmpt:value("network-redirect") +icmpt:value("host-redirect") +icmpt:value("TOS-network-redirect") +icmpt:value("TOS-host-redirect") +icmpt:value("echo-request") +icmpt:value("router-advertisement") +icmpt:value("router-solicitation") +icmpt:value("time-exceeded") +icmpt:value("ttl-zero-during-transit") +icmpt:value("ttl-zero-during-reassembly") +icmpt:value("parameter-problem") +icmpt:value("ip-header-bad") +icmpt:value("required-option-missing") +icmpt:value("timestamp-request") +icmpt:value("timestamp-reply") +icmpt:value("address-mask-request") +icmpt:value("address-mask-reply") + +src_ip = s:taboption("general", Value, "src_ip", translate("Source address")) +src_ip.optional = true +src_ip.datatype = has_v2 and "ipaddr" or "ip4addr" + +sport = s:taboption("general", Value, "src_port", translate("Source port")) +sport.optional = true +sport.datatype = "portrange" sport:depends("proto", "tcp") sport:depends("proto", "udp") sport:depends("proto", "tcpudp") -dport = s:option(Value, "dest_port", translate("Destination port")) +dest_ip = s:taboption("general", Value, "dest_ip", translate("Destination address")) +dest_ip.optional = true +dest_ip.datatype = has_v2 and "ipaddr" or "ip4addr" + +dport = s:taboption("general", Value, "dest_port", translate("Destination port")) +dport.optional = true +dport.datatype = "portrange" dport:depends("proto", "tcp") dport:depends("proto", "udp") dport:depends("proto", "tcpudp") -jump = s:option(ListValue, "target", translate("Action")) +jump = s:taboption("general", ListValue, "target", translate("Action")) jump.rmempty = true jump.default = "ACCEPT" jump:value("DROP", translate("drop")) @@ -74,4 +134,14 @@ jump:value("ACCEPT", translate("accept")) jump:value("REJECT", translate("reject")) +s:taboption("advanced", Value, "src_mac", translate("Source MAC-address")).optional = true + +if has_v2 then + family = s:taboption("advanced", ListValue, "family", translate("Restrict to address family")) + family.rmempty = true + family:value("", translate("IPv4 and IPv6")) + family:value("ipv4", translate("IPv4 only")) + family:value("ipv6", translate("IPv6 only")) +end + return m diff --git a/applications/luci-firewall/luasrc/model/cbi/luci_fw/zones.lua b/applications/luci-firewall/luasrc/model/cbi/luci_fw/zones.lua index edb82a9b50..f0e7b8665d 100644 --- a/applications/luci-firewall/luasrc/model/cbi/luci_fw/zones.lua +++ b/applications/luci-firewall/luasrc/model/cbi/luci_fw/zones.lua @@ -14,6 +14,9 @@ $Id$ local nw = require "luci.model.network" local fw = require "luci.model.firewall" +local ds = require "luci.dispatcher" + +local has_v2 = nixio.fs.access("/lib/firewall/fw.sh") require("luci.tools.webadmin") m = Map("firewall", translate("Firewall"), translate("The firewall creates zones over your network interfaces to control network traffic flow.")) @@ -25,18 +28,22 @@ s = m:section(TypedSection, "defaults") s.anonymous = true s.addremove = false -s:option(Flag, "syn_flood", translate("Enable SYN-flood protection")) +s:tab("general", translate("General Settings")) +s:tab("custom", translate("Custom Rules")) + -local di = s:option(Flag, "drop_invalid", translate("Drop invalid packets")) +s:taboption("general", Flag, "syn_flood", translate("Enable SYN-flood protection")) + +local di = s:taboption("general", Flag, "drop_invalid", translate("Drop invalid packets")) di.rmempty = false function di.cfgvalue(...) return AbstractValue.cfgvalue(...) or "1" end p = {} -p[1] = s:option(ListValue, "input", translate("Input")) -p[2] = s:option(ListValue, "output", translate("Output")) -p[3] = s:option(ListValue, "forward", translate("Forward")) +p[1] = s:taboption("general", ListValue, "input", translate("Input")) +p[2] = s:taboption("general", ListValue, "output", translate("Output")) +p[3] = s:taboption("general", ListValue, "forward", translate("Forward")) for i, v in ipairs(p) do v:value("REJECT", translate("reject")) @@ -44,14 +51,41 @@ for i, v in ipairs(p) do v:value("ACCEPT", translate("accept")) end +custom = s:taboption("custom", Value, "_custom", + translate("Custom Rules (/etc/firewall.user)")) + +custom.template = "cbi/tvalue" +custom.rows = 20 + +function custom.cfgvalue(self, section) + return nixio.fs.readfile("/etc/firewall.user") +end + +function custom.write(self, section, value) + nixio.fs.writefile("/etc/firewall.user", value) +end + s = m:section(TypedSection, "zone", translate("Zones")) s.template = "cbi/tblsection" s.anonymous = true s.addremove = true +s.extedit = ds.build_url("admin", "network", "firewall", "zones", "%s") -name = s:option(Value, "name", translate("Name")) -name.size = 8 +function s.create(self) + local z = fw:new_zone() + if z then + luci.http.redirect( + ds.build_url("admin", "network", "firewall", "zones", z.sid) + ) + end +end + +info = s:option(DummyValue, "_info", translate("Zone ⇒ Forwardings")) +info.template = "cbi/firewall_zoneforwards" +function info.cfgvalue(self, section) + return self.map:get(section, "name") +end p = {} p[1] = s:option(ListValue, "input", translate("Input")) @@ -67,15 +101,166 @@ end s:option(Flag, "masq", translate("Masquerading")) s:option(Flag, "mtu_fix", translate("MSS clamping")) -net = s:option(MultiValue, "network", translate("Network")) -net.template = "cbi/network_netlist" -net.widget = "checkbox" -net.rmempty = true -luci.tools.webadmin.cbi_add_networks(net) -function net.cfgvalue(self, section) - local value = MultiValue.cfgvalue(self, section) - return value or name:cfgvalue(section) +local created = nil + +-- +-- Redirects +-- + +s = m:section(TypedSection, "redirect", translate("Redirections")) +s.template = "cbi/tblsection" +s.addremove = true +s.anonymous = true +s.extedit = ds.build_url("admin", "network", "firewall", "redirect", "%s") + +function s.create(self, section) + created = TypedSection.create(self, section) +end + +function s.parse(self, ...) + TypedSection.parse(self, ...) + if created then + m.uci:save("firewall") + luci.http.redirect(ds.build_url( + "admin", "network", "firewall", "redirect", created + )) + end +end + +name = s:option(DummyValue, "_name", translate("Name")) +function name.cfgvalue(self, s) + return self.map:get(s, "_name") or "-" +end + +proto = s:option(DummyValue, "proto", translate("Protocol")) +function proto.cfgvalue(self, s) + local p = self.map:get(s, "proto") + if not p or p == "tcpudp" then + return "TCP+UDP" + else + return p:upper() + end +end + +src = s:option(DummyValue, "src", translate("Source")) +function src.cfgvalue(self, s) + local rv = "%s:%s:%s" % { + self.map:get(s, "src") or "*", + self.map:get(s, "src_ip") or "0.0.0.0/0", + self.map:get(s, "src_port") or "*" + } + + local mac = self.map:get(s, "src_mac") + if mac then + rv = rv .. ", MAC " .. mac + end + + return rv +end + +via = s:option(DummyValue, "via", translate("Via")) +function via.cfgvalue(self, s) + return "%s:%s:%s" % { + translate("Device"), + self.map:get(s, "src_dip") or "0.0.0.0/0", + self.map:get(s, "src_dport") or "*" + } +end + +dest = s:option(DummyValue, "dest", translate("Destination")) +function dest.cfgvalue(self, s) + return "%s:%s:%s" % { + self.map:get(s, "dest") or "*", + self.map:get(s, "dest_ip") or "0.0.0.0/0", + self.map:get(s, "dest_port") or "*" + } +end + +target = s:option(DummyValue, "target", translate("Action")) +function target.cfgvalue(self, s) + return self.map:get(s, "target") or "DNAT" +end + + +-- +-- Rules +-- + +s = m:section(TypedSection, "rule", translate("Rules")) +s.addremove = true +s.anonymous = true +s.template = "cbi/tblsection" +s.extedit = ds.build_url("admin", "network", "firewall", "rule", "%s") +s.defaults.target = "ACCEPT" + +function s.create(self, section) + local created = TypedSection.create(self, section) + m.uci:save("firewall") + luci.http.redirect(ds.build_url( + "admin", "network", "firewall", "rule", created + )) + return end +name = s:option(DummyValue, "_name", translate("Name")) +function name.cfgvalue(self, s) + return self.map:get(s, "_name") or "-" +end + +if has_v2 then + family = s:option(DummyValue, "family", translate("Family")) + function family.cfgvalue(self, s) + local f = self.map:get(s, "family") + if f and f:match("4") then + return translate("IPv4 only") + elseif f and f:match("6") then + return translate("IPv6 only") + else + return translate("IPv4 and IPv6") + end + end +end + +proto = s:option(DummyValue, "proto", translate("Protocol")) +function proto.cfgvalue(self, s) + local p = self.map:get(s, "proto") + local t = self.map:get(s, "icmp_type") + if p == "icmp" and t then + return "ICMP (%s)" % t + elseif p == "tcpudp" or not p then + return "TCP+UDP" + else + return p:upper() + end +end + +src = s:option(DummyValue, "src", translate("Source")) +function src.cfgvalue(self, s) + local rv = "%s:%s:%s" % { + self.map:get(s, "src") or "*", + self.map:get(s, "src_ip") or "0.0.0.0/0", + self.map:get(s, "src_port") or "*" + } + + local mac = self.map:get(s, "src_mac") + if mac then + rv = rv .. ", MAC " .. mac + end + + return rv +end + +dest = s:option(DummyValue, "dest", translate("Destination")) +function dest.cfgvalue(self, s) + return "%s:%s:%s" % { + self.map:get(s, "dest") or translate("Device"), + self.map:get(s, "dest_ip") or "0.0.0.0/0", + self.map:get(s, "dest_port") or "*" + } +end + + +s:option(DummyValue, "target", translate("Action")) + return m |