fw4: fix formatting of default log prefix
[project/firewall4.git] / root / usr / share / ucode / fw4.uc
index db1e580ba0e9ee70c1e7bdb00213d819be074203..2a1e397c2e84c9437319232760c528c36dd3e148 100644 (file)
@@ -541,7 +541,7 @@ return {
                        for (let ifc in ifaces.interface) {
                                let net = {
                                        up: ifc.up,
-                                       device: ifc.l3_device,
+                                       device: ifc.l3_device ?? ifc.device,
                                        physdev: ifc.device,
                                        zone: ifc.data?.zone
                                };
@@ -589,7 +589,7 @@ return {
                                                        ...rulespec,
 
                                                        name: (rulespec.type != 'ipset') ? `ubus:${ifc.interface}[${ifc.proto}] ${rulespec.type || 'rule'} ${n}` : rulespec.name,
-                                                       device: rulespec.device || ifc.l3_device
+                                                       device: rulespec.device ?? ifc.l3_device ?? ifc.device
                                                });
 
                                                n++;
@@ -726,6 +726,26 @@ return {
                this.cursor.foreach("firewall", "nat", n => self.parse_nat(n));
 
 
+               //
+               // Build list of includes
+               //
+
+               this.cursor.foreach("firewall", "include", i => self.parse_include(i));
+
+
+               //
+               // Discover automatic includes
+               //
+
+               if (this.default_option("auto_includes")) {
+                       for (let position in [ 'ruleset-pre', 'ruleset-post', 'table-pre', 'table-post', 'chain-pre', 'chain-post' ])
+                               for (let chain in (position in [ 'chain-pre', 'chain-post' ]) ? fs.lsdir(`/usr/share/nftables.d/${position}`) : [ null ])
+                                       for (let path in fs.glob(`/usr/share/nftables.d/${position}${chain ? `/${chain}` : ''}/*.nft`))
+                                               if (fs.access(path))
+                                                       this.parse_include({ type: 'nftables', position, chain, path });
+               }
+
+
                if (use_statefile) {
                        let fd = fs.open(STATEFILE, "w");
 
@@ -734,7 +754,8 @@ return {
                                        zones: this.state.zones,
                                        ipsets: this.state.ipsets,
                                        networks: this.state.networks,
-                                       ubus_rules: this.state.ubus_rules
+                                       ubus_rules: this.state.ubus_rules,
+                                       includes: this.state.includes
                                });
 
                                fd.close();
@@ -1037,7 +1058,7 @@ return {
                if (!rv)
                        return null;
 
-               let helper = filter(this.state.helpers, h => (h.name == rv.val))[0];
+               let helper = filter(this.state.helpers, h => (h.name == rv.val))?.[0];
 
                return helper ? { ...rv, ...helper } : null;
        },
@@ -1475,6 +1496,29 @@ return {
                return length(rv) ? rv : null;
        },
 
+       parse_includetype: function(val) {
+               return this.parse_enum(val, [
+                       "script",
+                       "nftables"
+               ]);
+       },
+
+       parse_includeposition: function(val) {
+               return replace(this.parse_enum(val, [
+                       "ruleset-prepend",
+                       "ruleset-postpend",
+                       "ruleset-append",
+
+                       "table-prepend",
+                       "table-postpend",
+                       "table-append",
+
+                       "chain-prepend",
+                       "chain-postpend",
+                       "chain-append"
+               ]), "postpend", "append");
+       },
+
        parse_string: function(val) {
                return "" + val;
        },
@@ -1703,6 +1747,36 @@ return {
                return this.state.ipsets;
        },
 
+       includes: function(position, chain) {
+               let stmts = [];
+               let pad = '';
+               let pre = '';
+
+               switch (position) {
+               case 'table-prepend':
+               case 'table-append':
+                       pad = '\t';
+                       pre = '\n';
+                       break;
+
+               case 'chain-prepend':
+               case 'chain-append':
+                       pad = '\t\t';
+                       break;
+
+               default:
+                       pre = '\n';
+               }
+
+               push(stmts, pre);
+
+               for (let inc in this.state.includes)
+                       if (inc.type == 'nftables' && inc.position == position && (!chain || inc.chain == chain))
+                               push(stmts, `${pad}include "${inc.path}"\n`);
+
+               print(length(stmts) > 1 ? join('', stmts) : '');
+       },
+
        parse_setfile: function(set, cb) {
                let fd = fs.open(set.loadfile, "r");
 
@@ -1815,7 +1889,9 @@ return {
                        custom_chains: [ "bool", null, UNSUPPORTED ],
                        disable_ipv6: [ "bool", null, UNSUPPORTED ],
                        flow_offloading: [ "bool", "0" ],
-                       flow_offloading_hw: [ "bool", "0" ]
+                       flow_offloading_hw: [ "bool", "0" ],
+
+                       auto_includes: [ "bool", "1" ]
                });
 
                if (defs.synflood_protect === null)
@@ -1945,7 +2021,6 @@ return {
                };
 
                let family = infer_family(zone.family, [
-                       zone.helper, "ct helper",
                        match_subnets, "subnet list"
                ]);
 
@@ -2183,6 +2258,7 @@ return {
                        set_dscp: [ "dscp", null, NO_INVERT ],
 
                        counter: [ "bool", "1" ],
+                       log: [ "string" ],
 
                        target: [ "target" ]
                });
@@ -2217,6 +2293,15 @@ return {
                        return;
                }
 
+               switch (this.parse_bool(rule.log)) {
+               case true:
+                       rule.log = `${rule.name}: `;
+                       break;
+
+               case false:
+                       delete rule.log;
+               }
+
                let ipset;
 
                if (rule.ipset) {
@@ -2489,6 +2574,7 @@ return {
                        reflection_zone: [ "zone_ref", null, PARSE_LIST ],
 
                        counter: [ "bool", "1" ],
+                       log: [ "string" ],
 
                        target: [ "target", "dnat" ]
                });
@@ -2507,6 +2593,15 @@ return {
                        redir.target = "dnat";
                }
 
+               switch (this.parse_bool(redir.log)) {
+               case true:
+                       redir.log = `${redir.name}: `;
+                       break;
+
+               case false:
+                       delete redir.log;
+               }
+
                let ipset;
 
                if (redir.ipset) {
@@ -2595,7 +2690,6 @@ return {
                        redir.dest.zone.dflags[redir.target] = true;
                }
 
-
                let add_rule = (family, proto, saddrs, daddrs, raddrs, sport, dport, rport, ipset, redir) => {
                        let r = {
                                ...redir,
@@ -2879,6 +2973,9 @@ return {
 
                        mark: [ "mark" ],
 
+                       counter: [ "bool", "1" ],
+                       log: [ "string" ],
+
                        target: [ "target", "masquerade" ]
                });
 
@@ -2919,6 +3016,15 @@ return {
                        return;
                }
 
+               switch (this.parse_bool(snat.log)) {
+               case true:
+                       snat.log = `${snat.name}: `;
+                       break;
+
+               case false:
+                       delete snat.log;
+               }
+
                let add_rule = (family, proto, saddrs, daddrs, raddrs, sport, dport, rport, snat) => {
                        let n = {
                                ...snat,
@@ -3012,6 +3118,74 @@ return {
                }
        },
 
+       parse_include: function(data) {
+               let inc = this.parse_options(data, {
+                       enabled: [ "bool", "1" ],
+
+                       path: [ "string", null, REQUIRED ],
+                       type: [ "includetype", "script" ],
+
+                       fw4_compatible: [ "bool", data.path != "/etc/firewall.user" ],
+
+                       family: [ "family", null, UNSUPPORTED ],
+                       reload: [ "bool", null, UNSUPPORTED ],
+
+                       position: [ "includeposition" ],
+                       chain: [ "string" ]
+               });
+
+               if (!inc.enabled) {
+                       this.warn_section(data, "is disabled, ignoring section");
+                       return;
+               }
+
+               if (inc.type == "script" && !inc.fw4_compatible) {
+                       this.warn_section(data, "is not marked as compatible with fw4, ignoring section");
+                       this.warn_section(data, "requires 'option fw4_compatible 1' to be considered compatible");
+                       return;
+               }
+
+               for (let opt in [ "table", "chain", "position" ]) {
+                       if (inc.type != "nftables" && inc[opt]) {
+                               this.warn_section(data, `must not specify '${opt}' for non-nftables includes, ignoring section`);
+                               return;
+                       }
+               }
+
+               switch (inc.position ??= 'table-append') {
+               case 'ruleset-prepend':
+               case 'ruleset-append':
+               case 'table-prepend':
+               case 'table-append':
+                       if (inc.chain)
+                               this.warn_section(data, `specifies 'chain' which has no effect for position ${inc.position}`);
+
+                       delete inc.chain;
+                       break;
+
+               case 'chain-prepend':
+               case 'chain-append':
+                       if (!inc.chain) {
+                               this.warn_section(data, `must specify 'chain' for position ${inc.position}, ignoring section`);
+                               return;
+                       }
+
+                       break;
+               }
+
+               let path = fs.readlink(inc.path) ?? inc.path;
+
+               if (!fs.access(path)) {
+                       this.warn_section(data, `specifies unreachable path '${path}', ignoring section`);
+                       return;
+               }
+
+               if (!data['.name'])
+                       this.warn(`Automatically including '${path}'`);
+
+               push(this.state.includes ||= [], { ...inc, path });
+       },
+
        parse_ipset: function(data) {
                let ipset = this.parse_options(data, {
                        enabled: [ "bool", "1" ],
@@ -3098,6 +3272,12 @@ return {
                        interval: interval
                };
 
+               if (s.interval)
+                       push(s.flags ??= [], 'interval');
+
+               if (s.timeout >= 0)
+                       push(s.flags ??= [], 'timeout');
+
                s.entries = filter(map(ipset.entry, (e) => {
                        let v = this.parse_ipsetentry(e, s);