1#!/usr/local/bin/perl
2# save_rule.cgi
3# Save, create or delete a rule in a chain
4
5require './firewall-lib.pl';
6&ReadParse();
7if (&get_ipvx_version() == 6) {
8	require './firewall6-lib.pl';
9	}
10else {
11	require './firewall4-lib.pl';
12	}
13&error_setup($text{'save_err'});
14@tables = &get_iptables_save();
15$table = $tables[$in{'table'}];
16&can_edit_table($table->{'name'}) || &error($text{'etable'});
17if ($in{'new'}) {
18	$rule = { 'chain' => $in{'chain'} };
19	}
20else {
21	$rule = $table->{'rules'}->[$in{'idx'}];
22	&can_jump($rule) || &error($text{'ejump'});
23	}
24if ($in{'clone'}) {
25	# Go back to the editing page
26	&redirect("edit_rule.cgi?version=${ipvx_arg}&new=1&clone=$in{'idx'}&".
27		  "table=".&urlize($in{'table'})."&".
28		  "after=$in{'idx'}&".
29		  "chain=".&urlize($rule->{'chain'}));
30	}
31
32&lock_file($ipvx_save);
33if ($in{'delete'}) {
34	# Just delete this rule
35	splice(@{$table->{'rules'}}, $in{'idx'}, 1);
36	}
37else {
38	# Validate and store inputs
39	if ($config{'comment_mod'}) {
40		$in{'cmt'} =~ s/^\s+//;
41		$in{'cmt'} =~ s/\s+$//;
42		if ($in{'cmt'}) {
43			$rule->{'comment'} = [ "", $in{'cmt'} ];
44			push(@mods, "comment");
45			}
46		else {
47			delete($rule->{'comment'});
48			}
49		}
50	else {
51		$rule->{'cmt'} = $in{'cmt'};
52		delete($rule->{'comment'});
53		@mods = grep { $_ ne "comment" } @mods;
54		}
55	if ($in{'jump'} eq '*') {
56		$in{'other'} =~ /^\S+$/ || &error($text{'save_echain'});
57		$rule->{'j'} = [ "", $in{'other'} ];
58		}
59	elsif ($in{'jump'}) {
60		$rule->{'j'} = [ "", $in{'jump'} ];
61		}
62	else {
63		delete($rule->{'j'});
64		}
65	&can_jump($rule) || &error($text{'save_ecanjump'});
66	if (defined($in{'rwithtype'})) {
67		if ($rule->{'j'}->[1] eq 'REJECT' && !$in{'rwithdef'}) {
68			$rule->{'reject-with'} = [ "", $in{'rwithtype'} ];
69			}
70		else {
71			delete($rule->{'reject-with'});
72			}
73		}
74
75	# Parse redirect or masquerade input
76	if ($table->{'name'} eq 'nat') {
77		if ($rule->{'j'}->[1] eq 'REDIRECT' && !$in{'rtodef'}) {
78			$in{'rtofrom'} =~ /^\d+$/ ||
79				&error($text{'save_ertoports'});
80			$in{'rtoto'} =~ /^\d*$/ ||
81				&error($text{'save_ertoports'});
82			$rule->{'to-ports'} = [ "", $in{'rtoto'} eq '' ?
83			    $in{'rtofrom'} : $in{'rtofrom'}."-".$in{'rtoto'} ];
84			}
85		elsif ($rule->{'j'}->[1] eq 'MASQUERADE' && !$in{'mtodef'}) {
86			$in{'mtofrom'} =~ /^\d+$/ ||
87				&error($text{'save_emtoports'});
88			$in{'mtoto'} =~ /^\d*$/ ||
89				&error($text{'save_emtoports'});
90			$rule->{'to-ports'} = [ "", $in{'mtoto'} eq '' ?
91			    $in{'mtofrom'} : $in{'mtofrom'}."-".$in{'mtoto'} ];
92			}
93		else {
94			delete($rule->{'to-ports'});
95			}
96		}
97	if ($table->{'name'} eq 'nat' && $rule->{'chain'} ne 'POSTROUTING') {
98		if ($rule->{'j'}->[1] eq 'DNAT' && !$in{'dnatdef'}) {
99			!$in{'dipfrom'} || &check_ipvx_ipaddress($in{'dipfrom'})||
100				&error($text{'save_edipfrom'});
101			!$in{'dipto'} || &check_ipvx_ipaddress($in{'dipto'}) ||
102				&error($text{'save_edipto'});
103			local $v = $in{'dipfrom'};
104			$v .= "-".$in{'dipto'} if ($in{'dipto'});
105			if ($in{'dpfrom'} ne '') {
106				$in{'dpfrom'} =~ /^\d+$/ ||
107					&error($text{'save_edpfrom'});
108				$in{'dpto'} =~ /^\d*$/ ||
109					&error($text{'save_edpto'});
110				if ($in{'dpto'} eq '') {
111					$v .= ":".$in{'dpfrom'};
112					}
113				else {
114					$v .= ":".$in{'dpfrom'}."-".$in{'dpto'};
115					}
116				}
117			$rule->{'to-destination'} = [ "", $v ];
118			}
119		else {
120			delete($rule->{'to-destination'});
121			}
122		}
123	if ($table->{'name'} eq 'nat' && $rule->{'chain'} ne 'PREROUTING' &&
124	    $rule->{'chain'} ne 'OUTPUT') {
125		if ($rule->{'j'}->[1] eq 'SNAT' && !$in{'snatdef'}) {
126			(!$in{'sipfrom'} && !$in{'sipto'}) ||
127			    &check_ipvx_ipaddress($in{'sipfrom'}) ||
128				&error($text{'save_esipfrom'});
129			!$in{'sipto'} || &check_ipvx_ipaddress($in{'sipto'}) ||
130				&error($text{'save_esipto'});
131			local $v = $in{'sipfrom'};
132			$v .= "-".$in{'sipto'} if ($in{'sipto'});
133			if ($in{'spfrom'} ne '') {
134				$in{'spfrom'} =~ /^\d+$/ ||
135					&error($text{'save_espfrom'});
136				$in{'spto'} =~ /^\d*$/ ||
137					&error($text{'save_espto'});
138				if ($in{'spto'} eq '') {
139					$v .= ":".$in{'spfrom'};
140					}
141				else {
142					$v .= ":".$in{'spfrom'}."-".$in{'spto'};
143					}
144				}
145			$rule->{'to-source'} = [ "", $v ];
146			}
147		else {
148			delete($rule->{'to-source'});
149			}
150		}
151	if (&parse_mode("source", $rule, "s")) {
152		&check_ipmask($in{'source'}) || &error($text{'save_esource'});
153		$rule->{'s'}->[1] = join(",", split(/[ \t\r\n,]+/, $in{'source'}));
154		}
155	if (&parse_mode("dest", $rule, "d")) {
156		&check_ipmask($in{'dest'}) || &error($text{'save_edest'});
157		$rule->{'d'}->[1] = join(",", split(/[ \t\r\n,]+/, $in{'dest'}));
158		}
159	if (&parse_mode("in", $rule, "i")) {
160		$in{'in'} ne '' || $in{'in_other'} =~ /^\S+$/ ||
161			&error($text{'save_ein'});
162		$rule->{'i'}->[1] = $in{'in'} eq '' || $in{'in'} eq 'other' ?
163					$in{'in_other'} : $in{'in'};
164		}
165	if (&parse_mode("out", $rule, "o")) {
166		$in{'out'} ne '' || $in{'out_other'} =~ /^\S+$/ ||
167			&error($text{'save_eout'});
168		$rule->{'o'}->[1] = $in{'out'} eq '' || $in{'out'} eq 'other' ?
169					$in{'out_other'} : $in{'out'};
170		}
171	if ($in{'frag'} == 0) { delete($rule->{'f'}); }
172	elsif ($in{'frag'} == 1) { $rule->{'f'} = [ "" ]; }
173	else { $rule->{'f'} = [ "!" ]; }
174	if (&parse_mode("proto", $rule, "p")) {
175		$in{'proto'} || $in{'proto_other'} =~ /^\d+$/ ||
176			&error($text{'save_eproto'});
177		$rule->{'p'}->[1] = $in{'proto'} || $in{'proto_other'};
178		if (!$rule->{'p'}->[0]) {
179			$proto = $in{'proto'};
180			push(@mods, $in{'proto'})
181				if ($proto eq 'tcp' || $proto eq 'udp' ||
182				    $proto eq "icmp${ipvx_icmp}" && $in{'icmptype_mode'});
183			}
184		}
185
186	if (&parse_mode("sport", $rule, "sport")) {
187		$proto eq "tcp" || $proto eq "udp" || $proto eq "sctp" ||
188			&error($text{'save_etcpudp'});
189		if ($in{"sport_type"} == 0) {
190			$in{"sport"} =~ /^\S+$/ ||
191				&error($text{'save_esport'});
192			if ($in{"sport"} =~ /,/) {
193				$rule->{'sports'}->[1] = $in{"sport"};
194				$rule->{'sports'}->[0] = $rule->{'sport'}->[0];
195				push(@mods, "multiport");
196				delete($rule->{'sport'});
197				}
198			else {
199				$rule->{'sport'}->[1] = $in{"sport"};
200				delete($rule->{'sports'});
201				}
202			}
203		else {
204			$in{"sport_from"} =~ /^\d*$/ ||
205				&error($text{'save_esportfrom'});
206			$in{"sport_to"} =~ /^\d*$/ ||
207				&error($text{'save_esportto'});
208			$rule->{'sport'}->[1] = $in{"sport_from"}.":".
209						$in{"sport_to"};
210			$rule->{'sport'}->[1] eq ":" &&
211				&error($text{'save_esportrange'});
212			delete($rule->{'sports'});
213			}
214		}
215	else {
216		delete($rule->{'sports'});
217		}
218	if (&parse_mode("dport", $rule, "dport")) {
219		$proto eq "tcp" || $proto eq "udp" || $proto eq "sctp" ||
220			&error($text{'save_etcpudp'});
221		if ($in{"dport_type"} == 0) {
222			$in{"dport"} =~ /^\S+$/ ||
223				&error($text{'save_edport'});
224			if ($in{"dport"} =~ /,/) {
225				$rule->{'dports'}->[1] = $in{"dport"};
226				$rule->{'dports'}->[0] = $rule->{'dport'}->[0];
227				push(@mods, "multiport");
228				delete($rule->{'dport'});
229				}
230			else {
231				$rule->{'dport'}->[1] = $in{"dport"};
232				delete($rule->{'dports'});
233				}
234			}
235		else {
236			$in{"dport_from"} =~ /^\d*$/ ||
237				&error($text{'save_edportfrom'});
238			$in{"dport_to"} =~ /^\d*$/ ||
239				&error($text{'save_edportto'});
240			$rule->{'dport'}->[1] = $in{"dport_from"}.":".
241						$in{"dport_to"};
242			$rule->{'dport'}->[1] eq ":" &&
243				&error($text{'save_edportrange'});
244			delete($rule->{'dports'});
245			}
246		}
247	else {
248		delete($rule->{'dports'});
249		}
250	if (&parse_mode("ports", $rule, "ports")) {
251		$proto eq "tcp" || $proto eq "udp" || $proto eq "sctp" ||
252			&error($text{'save_etcpudp'});
253		$in{"ports"} =~ /^\S+$/ || &error($text{'save_eports'});
254		$rule->{'ports'}->[1] = $in{'ports'};
255		push(@mods, "multiport");
256		}
257	if (&parse_mode("tcpflags", $rule, "tcp-flags")) {
258		$proto eq "tcp" || &error($text{'save_etcp1'});
259		local $tcp0 = join(",", split(/\0/, $in{"tcpflags0"}));
260		local $tcp1 = join(",", split(/\0/, $in{"tcpflags1"}));
261		#$tcp0 && $tcp1 || &error($text{'save_etcpflags'});
262		$tcp0 || &error($text{'save_etcpflags2'});
263		$rule->{'tcp-flags'}->[1] = $tcp0;
264		$rule->{'tcp-flags'}->[2] = $tcp1 || "NONE";
265		}
266	if (&parse_mode("tcpoption", $rule, "tcp-option")) {
267		$proto eq "tcp" || &error($text{'save_etcp2'});
268		$in{"tcpoption"} =~ /^\d+$/ ||
269			&error($text{'save_etcpoption'});
270		$rule->{'tcp-option'}->[1] = $in{"tcpoption"};
271		}
272	if (&parse_mode("icmptype", $rule, "icmp${ipvx_icmp}-type")) {
273		$proto eq "icmp${ipvx_icmp}" || &error($text{'save_eicmp'});
274		$rule->{"icmp${ipvx_icmp}-type"}->[1] = $in{'icmptype'};
275		}
276	if (&parse_mode("macsource", $rule, "mac-source")) {
277		$in{"macsource"} =~ /^([0-9a-z]{2}:){5}[[0-9a-z]{2}$/i ||
278			&error($text{'save_emac'});
279		$rule->{'mac-source'}->[1] = $in{'macsource'};
280		push(@mods, "mac");
281		}
282	if (&parse_mode("limit", $rule, "limit")) {
283		$in{'limit0'} =~ /^\d+$/ || &error($text{'save_elimit'});
284		$rule->{'limit'}->[1] = $in{'limit0'}."/".$in{'limit1'};
285		push(@mods, "limit");
286		}
287	if (&parse_mode("limitburst", $rule, "limit-burst")) {
288		$in{'limitburst'} =~ /^\d+$/ ||
289			&error($text{'save_elimitburst'});
290		$rule->{'limit-burst'}->[1] = $in{'limitburst'};
291		push(@mods, "limit");
292		}
293
294	if ($rule->{'chain'} eq 'OUTPUT') {
295		if (&parse_mode("uidowner", $rule, "uid-owner")) {
296			defined(getpwnam($in{"uidowner"})) ||
297				&error($text{'save_euidowner'});
298			$rule->{'uid-owner'}->[1] = $in{"uidowner"};
299			push(@mods, "owner");
300			}
301		if (&parse_mode("gidowner", $rule, "gid-owner")) {
302			defined(getgrnam($in{"gidowner"})) ||
303				&error($text{'save_egidowner'});
304			$rule->{'gid-owner'}->[1] = $in{"gidowner"};
305			push(@mods, "owner");
306			}
307		if (&parse_mode("pidowner", $rule, "pid-owner")) {
308			$in{"pidowner"} =~ /^\d+$/ ||
309				&error($text{'save_epidowner'});
310			$rule->{'pid-owner'}->[1] = $in{"pidowner"};
311			push(@mods, "owner");
312			}
313		if (&parse_mode("sidowner", $rule, "sid-owner")) {
314			$in{"sidowner"} =~ /^\d+$/ ||
315				&error($text{'save_esidowner'});
316			$rule->{'sid-owner'}->[1] = $in{"sidowner"};
317			push(@mods, "owner");
318			}
319		}
320
321	# Save connection states and TOS
322	my $sd = &supports_conntrack() ? "ctstate" : "state";
323	my $nonsd = $sd eq "ctstate" ? "state" : "ctstate";
324	if (&parse_mode($sd, $rule, $sd)) {
325		@states = split(/\0/, $in{$sd});
326		@states || &error($text{'save_estates'});
327		$rule->{$sd}->[1] = join(",", @states);
328		push(@mods, $sd eq "state" ? "state" : "conntrack");
329		delete($rule->{$nonsd});
330		}
331	if (&parse_mode("tos", $rule, "tos")) {
332		$rule->{'tos'}->[1] = $in{'tos'};
333		push(@mods, "tos");
334		}
335
336	# Parse physical input and output interfaces
337	if (&parse_mode("physdevin", $rule, "physdev-in")) {
338		$in{'physdevin'} ne '' || $in{'physdevin_other'} =~ /^\S+$/ ||
339			&error($text{'save_ephysdevin'});
340		$rule->{'physdev-in'}->[1] =
341		  $in{'physdevin'} eq '' || $in{'physdevin'} eq 'other' ?
342			$in{'physdevin_other'} : $in{'physdevin'};
343		push(@mods, "physdev");
344		}
345	if (&parse_mode("physdevout", $rule, "physdev-out")) {
346		$in{'physdevout'} ne '' || $in{'physdevout_other'} =~ /^\S+$/ ||
347			&error($text{'save_ephysdevout'});
348		$rule->{'physdev-out'}->[1] =
349		  $in{'physdevout'} eq '' || $in{'physdevout'} eq 'other' ?
350			$in{'physdevout_other'} : $in{'physdevout'};
351		push(@mods, "physdev");
352		}
353
354	# Parse physdev match modes
355	if (&parse_mode("physdevisin", $rule, "physdev-is-in")) {
356		push(@mods, "physdev");
357		}
358	if (&parse_mode("physdevisout", $rule, "physdev-is-out")) {
359		push(@mods, "physdev");
360		}
361	if (&parse_mode("physdevisbridged", $rule, "physdev-is-bridged")) {
362		push(@mods, "physdev");
363		}
364
365	# Add custom parameters and modules
366	$rule->{'args'} = $in{'args'};
367	push(@mods, split(/\s+/, $in{'mods'}));
368
369	# Save the rule
370	if (@mods) {
371		$rule->{'m'} = [ map { [ "", $_ ] } &unique(@mods) ];
372		}
373	else {
374		delete($rule->{'m'});
375		}
376	delete($rule->{'j'}) if (!$in{'jump'});
377	if ($in{'new'}) {
378		if ($in{'before'} ne '') {
379			splice(@{$table->{'rules'}}, $in{'before'}, 0, $rule);
380			}
381		elsif ($in{'after'} ne '') {
382			splice(@{$table->{'rules'}}, $in{'after'}+1, 0, $rule);
383			}
384		else {
385			push(@{$table->{'rules'}}, $rule);
386			}
387		}
388	}
389
390# Write out the new save file
391&run_before_command();
392&save_table($table);
393&run_after_command();
394&copy_to_cluster();
395&unlock_file($ipvx_save);
396&webmin_log($in{'delete'} ? "delete" : $in{'new'} ? "create" : "modify",
397	    "rule", undef, { 'chain' => $rule->{'chain'},
398			     'table' => $table->{'name'} });
399&redirect("index.cgi?version=${ipvx_arg}&table=$in{'table'}");
400
401# parse_mode(name, &rule, option)
402sub parse_mode
403{
404if ($in{"$_[0]_mode"} == 0) {
405	delete($_[1]->{$_[2]});
406	return 0;
407	}
408elsif ($in{"$_[0]_mode"} == 1) {
409	$_[1]->{$_[2]} = [ "" ];
410	return 1;
411	}
412else {
413	$_[1]->{$_[2]} = [ "!" ];
414	return 1;
415	}
416}
417
418
419