Initial commit
[project/firewall4.git] / root / usr / share / firewall4 / main.uc
1 {%
2
3 /* Find existing sets.
4 *
5 * Unfortunately, terse mode (-t) is incompatible with JSON output so
6 * we either need to parse a potentially huge JSON just to get the set
7 * header data or scrape the ordinary nft output to obtain the same
8 * information. Opt for the latter to avoid parsing potentially huge
9 * JSON documents.
10 */
11 function find_existing_sets() {
12 let fd = fs.popen("nft -t list sets inet", "r");
13
14 if (!fd) {
15 warn(sprintf("Unable to execute 'nft' for listing existing sets: %s\n",
16 fs.error()));
17 return {};
18 }
19
20 let line, table, set;
21 let sets = {};
22
23 while ((line = fd.read("line")) !== "") {
24 let m;
25
26 if ((m = match(line, /^table inet (.+) \{\n$/)) != null) {
27 table = m[1];
28 }
29 else if ((m = match(line, /^\tset (.+) \{\n$/)) != null) {
30 set = m[1];
31 }
32 else if ((m = match(line, /^\t\ttype (.+)\n$/)) != null) {
33 if (table == "fw4" && set)
34 sets[set] = split(m[1], " . ");
35
36 set = null;
37 }
38 }
39
40 fd.close();
41
42 return sets;
43 }
44
45 function read_state() {
46 let state = fw4.read_state();
47
48 if (!state) {
49 warn("Unable to read firewall state - do you need to start the firewall?\n");
50 exit(1);
51 }
52
53 return state;
54 }
55
56 function reload_sets() {
57 let state = read_state(),
58 sets = find_existing_sets();
59
60 for (let set in state.ipsets) {
61 if (!set.loadfile || !length(set.entries))
62 continue;
63
64 if (!exists(sets, set.name)) {
65 warn(sprintf("Named set '%s' does not exist - do you need to restart the firewall?\n",
66 set.name));
67 continue;
68 }
69 else if (fw4.concat(sets[set.name]) != fw4.concat(set.types)) {
70 warn(sprintf("Named set '%s' has a different type - want '%s' but is '%s' - do you need to restart the firewall?\n",
71 set.name, fw4.concat(set.types), fw4.concat(sets[set.name])));
72 continue;
73 }
74
75 let first = true;
76 let printer = (entry) => {
77 if (first) {
78 print("add element inet fw4 ", set.name, " {\n");
79 first = false;
80 }
81
82 print("\t", join(" . ", entry), ",\n");
83 };
84
85 print("flush set inet fw4 ", set.name, "\n");
86
87 map(set.entries, printer);
88 fw4.parse_setfile(set, printer);
89
90 if (!first)
91 print("}\n\n");
92 }
93 }
94
95 function render_ruleset(use_statefile) {
96 fw4.load(use_statefile);
97
98 include("templates/ruleset.uc", { fw4, type, exists, length, include });
99 }
100
101 function lookup_network(net) {
102 let state = read_state();
103
104 for (let zone in state.zones) {
105 for (let network in (zone.network || [])) {
106 if (network.device == net) {
107 print(zone.name, "\n");
108 exit(0);
109 }
110 }
111 }
112
113 exit(1);
114 }
115
116 function lookup_device(dev) {
117 let state = read_state();
118
119 for (let zone in state.zones) {
120 for (let rule in (zone.match_rules || [])) {
121 if (dev in rule.devices_pos) {
122 print(zone.name, "\n");
123 exit(0);
124 }
125 }
126 }
127
128 exit(1);
129 }
130
131 function lookup_zone(name, dev) {
132 let state = read_state();
133
134 for (let zone in state.zones) {
135 if (zone.name == name) {
136 let devices = [];
137 map(zone.match_rules, (r) => push(devices, ...(r.devices_pos || [])));
138
139 if (dev) {
140 if (dev in devices) {
141 print(dev, "\n");
142 exit(0);
143 }
144
145 exit(1);
146 }
147
148 if (length(devices))
149 print(join("\n", devices), "\n");
150
151 exit(0);
152 }
153 }
154
155 exit(1);
156 }
157
158
159 switch (getenv("ACTION")) {
160 case "start":
161 return render_ruleset(true);
162
163 case "print":
164 return render_ruleset(false);
165
166 case "reload-sets":
167 return reload_sets();
168
169 case "network":
170 return lookup_network(getenv("OBJECT"));
171
172 case "device":
173 return lookup_device(getenv("OBJECT"));
174
175 case "zone":
176 return lookup_zone(getenv("OBJECT"), getenv("DEVICE"));
177 }