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