1#!/usr/local/bin/perl
2# edit_rule.cgi
3# Display the details of one firewall rule, or allow the adding of a new one
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@tables = &get_iptables_save();
14$table = $tables[$in{'table'}];
15&can_edit_table($table->{'name'}) || &error($text{'etable'});
16if ($in{'clone'} ne '') {
17	&ui_print_header($text{"index_title_v${ipvx}"}, $text{'edit_title3'}, "");
18	%clone = %{$table->{'rules'}->[$in{'clone'}]};
19	$rule = \%clone;
20	}
21elsif ($in{'new'}) {
22	&ui_print_header($text{"index_title_v${ipvx}"}, $text{'edit_title1'}, "");
23	$rule = { 'chain' => $in{'chain'},
24		  'j' => &can_jump('DROP') ? 'DROP' : "" };
25	}
26else {
27	&ui_print_header($text{"index_title_v${ipvx}"}, $text{'edit_title2'}, "");
28	$rule = $table->{'rules'}->[$in{'idx'}];
29	&can_jump($rule) || &error($text{'ejump'});
30	}
31
32print &ui_form_start("save_rule${ipvx}.cgi", "post");
33print &ui_hidden("version", ${ipvx_arg});
34foreach $f ('table', 'idx', 'new', 'chain', 'before', 'after') {
35	print &ui_hidden($f, $in{$f});
36	}
37
38# Display action section
39print &ui_table_start($text{'edit_header1'}, "width=100%", 2);
40
41print &ui_table_row($text{'edit_chain'},
42	$text{"index_chain_".lc($rule->{'chain'})} ||
43	&text('index_chain', "<tt>$rule->{'chain'}</tt>"));
44
45# Rule comment
46if ($config{'comment_mod'} || $rule->{'comment'}) {
47	# Get comment from --comment option
48	$cmt = $rule->{'comment'}->[1];
49	}
50else {
51	# Get comment from # at end of line
52	$cmt = $rule->{'cmt'};
53	}
54print &ui_table_row($text{'edit_cmt'},
55	&ui_textbox("cmt", $cmt, 50));
56
57# Action to take or chain to jump to
58if ($table->{'name'} eq 'nat') {
59	@jumps = ( undef, 'ACCEPT', 'DROP' );
60	if ($rule->{'chain'} eq 'POSTROUTING') {
61		push(@jumps, 'MASQUERADE', 'SNAT');
62		}
63	elsif ($rule->{'chain'} eq 'PREROUTING' ||
64	       $rule->{'chain'} eq 'OUTPUT') {
65		push(@jumps, 'REDIRECT', 'DNAT');
66		}
67	else {
68		push(@jumps, 'MASQUERADE', 'SNAT', 'REDIRECT', 'DNAT');
69		}
70	}
71else {
72	@jumps = ( undef, 'ACCEPT', 'DROP', 'REJECT', 'QUEUE', 'RETURN', 'LOG' );
73	}
74@grid = ( );
75$found = 0;
76foreach $j (grep { &can_jump($_) } @jumps) {
77	push(@grid, &ui_oneradio("jump", $j, $text{"index_jump_".lc($j)},
78				 $rule->{'j'}->[1] eq $j));
79	$found++ if ($rule->{'j'}->[1] eq $j);
80	}
81push(@grid, &ui_oneradio("jump", "*", $text{'edit_jump_other'}, !$found));
82push(@grid, &ui_textbox("other", $found ? "" : $rule->{'j'}->[1], 12));
83print &ui_table_row($text{'edit_jump'},
84	&ui_grid_table(\@grid, 6, undef));
85
86if (&indexof('REJECT', @jumps) >= 0 && &can_jump("REJECT")) {
87	# Show input for REJECT icmp type
88	if ($rule->{'j'}->[1] eq 'REJECT') {
89		$rwith = $rule->{'reject-with'}->[1];
90		}
91	print &ui_table_row($text{'edit_rwith'},
92		&ui_radio("rwithdef", $rwith eq "" ? 1 : 0,
93			  [ [ 1, $text{'default'} ],
94			    [ 0, &text('edit_rwithtype',
95			      &icmptype_input("rwithtype", $rwith, \@ipvx_rtypes)) ],
96			  ]));
97	}
98
99if (($table->{'name'} eq 'nat' && $rule->{'chain'} ne 'POSTROUTING') &&
100     &can_jump("REDIRECT")) {
101	# Show inputs for redirect host and port
102	if ($rule->{'j'}->[1] eq 'REDIRECT') {
103		($rtofrom, $rtoto) = split(/\-/, $rule->{'to-ports'}->[1]);
104		}
105	print &ui_table_row($text{'edit_rtoports'},
106		&ui_radio("rtodef", $rtofrom eq "" ? 1 : 0,
107			  [ [ 1, $text{'default'} ],
108			    [ 0, &text('edit_prange',
109				       &ui_textbox("rtofrom", $rtofrom, 6),
110				       &ui_textbox("rtoto", $rtoto, 6)) ] ]));
111	}
112
113if (($table->{'name'} eq 'nat' && $rule->{'chain'} ne 'PREROUTING' &&
114     $rule->{'chain'} ne 'OUTPUT') &&
115    &can_jump("MASQUERADE")) {
116	# Show inputs for masquerading ports
117	if ($rule->{'j'}->[1] eq 'MASQUERADE') {
118		($mtofrom, $mtoto) = split(/\-/, $rule->{'to-ports'}->[1]);
119		}
120	print &ui_table_row($text{'edit_mtoports'},
121		&ui_radio("mtodef", $mtofrom eq "" ? 1 : 0,
122			  [ [ 1, $text{'edit_any'} ],
123			    [ 0, &text('edit_prange',
124				       &ui_textbox("mtofrom", $mtofrom, 6),
125				       &ui_textbox("mtoto", $mtoto, 6)) ] ]));
126	}
127
128if (($table->{'name'} eq 'nat' && $rule->{'chain'} ne 'POSTROUTING') &&
129    &can_jump("DNAT")) {
130	if ($rule->{'j'}->[1] eq 'DNAT') {
131		if ($rule->{'to-destination'}->[1] =~
132		    /$ipvx_todestpattern/) {
133			$dipfrom = $1;
134			$dipto = $3;
135			$dpfrom = $5;
136			$dpto = $7;
137			}
138		elsif ($rule->{'to-destination'}->[1] =~ /^(:(\d+)(\-(\d+))?)?$/) {
139			$dipfrom = "";
140			$dipto = "";
141			$dpfrom = $2;
142			$dpto = $4;
143			}
144		}
145	print &ui_table_row($text{'edit_dnat'},
146		&ui_radio("dnatdef", $dipfrom eq "" ? 1 : 0,
147			  [ [ 1, $text{'default'} ],
148			    [ 0, &text('edit_dnatip',
149				   &ui_textbox("dipfrom", $dipfrom, 15),
150				   &ui_textbox("dipto", $dipto, 15))." ".
151				 &text('edit_prange',
152				   &ui_textbox("dpfrom", $dpfrom, 6),
153				   &ui_textbox("dpto", $dpto, 6)) ] ]));
154	}
155
156if (($table->{'name'} eq 'nat' && $rule->{'chain'} ne 'PREROUTING' &&
157     $rule->{'chain'} ne 'OUTPUT') &&
158    &can_jump("SNAT")) {
159	if ($rule->{'j'}->[1] eq 'SNAT') {
160		if ($rule->{'to-source'}->[1] =~
161		    /^([0-9\.]+)?(\-([0-9\.]+))?(:(\d+)(\-(\d+))?)?$/) {
162			$sipfrom = $1;
163			$sipto = $3;
164			$spfrom = $5;
165			$spto = $7;
166			}
167		}
168	print &ui_table_row($text{'edit_snat'},
169		&ui_radio("snatdef", $sipfrom eq "" ? 1 : 0,
170			  [ [ 1, $text{'default'} ],
171			    [ 0, &text('edit_dnatip',
172				   &ui_textbox("sipfrom", $sipfrom, 15),
173				   &ui_textbox("sipto", $sipto, 15))." ".
174				 &text('edit_prange',
175				   &ui_textbox("spfrom", $spfrom, 6),
176				   &ui_textbox("spto", $spto, 6)) ] ]));
177	}
178
179print &ui_table_end();
180
181# Display conditions section
182print "$text{'edit_desc'}<br>\n";
183print &ui_table_start($text{'edit_header2'}, "width=100%", 2);
184
185# Packet source
186print &ui_table_row($text{'edit_source'},
187	&ui_grid_table([
188		&print_mode("source", $rule->{'s'}),
189		&ui_textarea("source", join(" ", split(/,/, $rule->{'s'}->[1])),
190			     4, 60),
191		], 2));
192
193# Packet destination
194print &ui_table_row($text{'edit_dest'},
195	&ui_grid_table([
196		&print_mode("dest", $rule->{'d'}),
197		&ui_textarea("dest", join(" ", split(/,/, $rule->{'d'}->[1])),
198			     4, 60),
199		], 2));
200
201# Incoming interface
202print &ui_table_row($text{'edit_in'},
203	&print_mode("in", $rule->{'i'})." ".
204	&interface_choice("in", $rule->{'i'}->[1]));
205
206# Outgoing interface
207print &ui_table_row($text{'edit_out'},
208	&print_mode("out", $rule->{'o'})." ".
209	&interface_choice("out", $rule->{'o'}->[1]));
210
211# Packet fragmentation
212$f = !$rule->{'f'} ? 0 : $rule->{'f'}->[0] eq "!" ? 2 : 1;
213print &ui_table_row($text{'edit_frag'},
214	&ui_radio("frag", $f, [ [ 0, $text{'edit_ignore'} ],
215				[ 1, $text{'edit_fragis'} ],
216				[ 2, $text{'edit_fragnot'} ] ]));
217
218# IP protocol
219print &ui_table_row($text{'edit_proto'},
220	&print_mode("proto", $rule->{'p'})." ".
221	&protocol_input("proto", $rule->{'p'}->[1]));
222
223print &ui_table_hr();
224
225# Source port
226print &ui_table_row($text{'edit_sport'},
227	&print_mode("sport", $rule->{'sports'} || $rule->{'sport'})." ".
228	&port_input("sport", $rule->{'sports'}->[1] || $rule->{'sport'}->[1]));
229
230# Destination port
231print &ui_table_row($text{'edit_dport'},
232	&print_mode("dport", $rule->{'dports'} || $rule->{'dport'})." ".
233	&port_input("dport", $rule->{'dports'}->[1] || $rule->{'dport'}->[1]));
234
235# Source and destination ports
236print &ui_table_row($text{'edit_ports'},
237	&print_mode("ports", $rule->{'ports'})." ".
238	&ui_textbox("ports", $rule->{'ports'}->[1], 30));
239
240# TCP flags
241print &ui_table_row($text{'edit_tcpflags'},
242	"<table><tr><td>".&print_mode("tcpflags", $rule->{'tcp-flags'}).
243	"</td> <td>".&text('edit_flags',
244	    &tcpflag_input("tcpflags0", $rule->{'tcp-flags'}->[1]),
245	    &tcpflag_input("tcpflags1", $rule->{'tcp-flags'}->[2])).
246	"</td></tr></table>");
247
248# TCP options
249print &ui_table_row($text{'edit_tcpoption'},
250	&print_mode("tcpoption", $rule->{'tcp-option'})." ".
251	&ui_textbox("tcpoption", $rule->{'tcp-option'}->[1], 6));
252
253print &ui_table_hr();
254
255# ICMP packet type
256print &ui_table_row($text{'edit_icmptype'},
257	&print_mode("icmptype", $rule->{"icmp${ipvx_icmp}-type"})." ".
258	&icmptype_input("icmptype", $rule->{"icmp${ipvx_icmp}-type"}->[1]));
259
260# MAC address
261print &ui_table_row($text{'edit_mac'},
262	&print_mode("macsource", $rule->{'mac-source'})." ".
263	&ui_textbox("macsource", $rule->{'mac-source'}->[1], 18));
264
265print &ui_table_hr();
266
267# Packet flow limit
268($n, $u) = $rule->{'limit'} &&
269	   $rule->{'limit'}->[1] =~ /^(\d+)\/(\S+)$/ ? ($1, $2) : ();
270print &ui_table_row($text{'edit_limit'},
271	&print_mode("limit", $rule->{'limit'},
272		    $text{'edit_below'}, $text{'edit_above'}, 1)." ".
273	&ui_textbox("limit0", $n, 6)." / ".
274	&ui_select("limit1", $u, ['second', 'minute', 'hour', 'day']));
275
276# Packet burst rate
277print &ui_table_row($text{'edit_limitburst'},
278	&print_mode("limitburst", $rule->{'limit-burst'},
279		    $text{'edit_below'}, $text{'edit_above'}, 1)." ".
280	&ui_textbox("limitburst", $rule->{'limit-burst'}->[1], 6));
281
282if ($rule->{'chain'} eq 'OUTPUT') {
283	print &ui_table_hr();
284
285	# Sending UID
286	print &ui_table_row($text{'edit_uidowner'},
287		&print_mode("uidowner", $rule->{'uid-owner'})." ".
288		&ui_user_textbox("uidowner", $rule->{'uid-owner'}->[1]));
289
290	# Sending GID
291	print &ui_table_row($text{'edit_gidowner'},
292		&print_mode("gidowner", $rule->{'gid-owner'})." ".
293		&ui_group_textbox("gidowner", $rule->{'gid-owner'}->[1]));
294
295	# Sending process ID
296	print &ui_table_row($text{'edit_pidowner'},
297		&print_mode("pidowner", $rule->{'pid-owner'})." ".
298		&ui_textbox("pidowner", $rule->{'pid-owner'}->[1], 6));
299
300	# Sending process group
301	print &ui_table_row($text{'edit_sidowner'},
302		&print_mode("sidowner", $rule->{'sid-owner'})." ".
303		&ui_textbox("sidowner", $rule->{'sid-owner'}->[1], 6));
304	}
305
306print &ui_table_hr();
307
308# Connection states
309my $sd = &supports_conntrack() ? "ctstate" : "state";
310print &ui_table_row($text{'edit_state'},
311	"<table cellpadding=0 cellspacing=0><tr><td valign=top>".
312	&print_mode($sd, $rule->{$sd})."</td>\n".
313	"<td>&nbsp;".
314	&ui_select($sd, [ split(/,/, $rule->{$sd}->[1]) ],
315	   [ map { [ $_, $text{"edit_state_".lc($_)} ] }
316		 ('NEW', 'ESTABLISHED', 'RELATED', 'INVALID', 'UNTRACKED',
317		  $sd eq "state" ? ( ) : ('SNAT', 'DNAT')) ], 5, 1).
318	"</td></tr></table>");
319
320# Type of service
321print &ui_table_row($text{'edit_tos'},
322	&print_mode("tos", $rule->{'tos'})." ".
323	&tos_input("tos", $rule->{'tos'}->[1]));
324
325print &ui_table_hr();
326
327# Input physical device
328print &ui_table_row($text{'edit_physdevin'},
329	&print_mode("physdevin", $rule->{'physdev-in'})." ".
330	&interface_choice("physdevin", $rule->{'physdev-in'}->[1]));
331
332# Output physical device
333print &ui_table_row($text{'edit_physdevout'},
334	&print_mode("physdevout", $rule->{'physdev-out'})." ".
335	&interface_choice("physdevout", $rule->{'physdev-out'}->[1]));
336
337# Physdev match modes
338print &ui_table_row($text{'edit_physdevisin'},
339	&print_mode("physdevisin", $rule->{'physdev-is-in'},
340		    $text{'yes'}, $text{'no'}));
341print &ui_table_row($text{'edit_physdevisout'},
342	&print_mode("physdevisout", $rule->{'physdev-is-out'},
343		    $text{'yes'}, $text{'no'}));
344print &ui_table_row($text{'edit_physdevisbridged'},
345	&print_mode("physdevisbridged", $rule->{'physdev-is-bridged'},
346		    $text{'yes'}, $text{'no'}));
347
348print &ui_table_hr();
349
350# Show unknown modules
351@mods = grep { !/^(tcp|udp|icmp${ipvx_icmp}|multiport|mac|limit|owner|state|conntrack|tos|comment|physdev)$/ } map { $_->[1] } @{$rule->{'m'}};
352print &ui_table_row($text{'edit_mods'},
353	&ui_textbox("mods", join(" ", @mods), 60));
354
355# Show unknown parameters
356$rule->{'args'} =~ s/^\s+//;
357$rule->{'args'} =~ s/\s+$//;
358print &ui_table_row($text{'edit_args'},
359	&ui_textbox("args", $rule->{'args'}, 60));
360
361print &ui_table_end();
362if ($in{'new'}) {
363	print &ui_form_end([ [ undef, $text{'create'} ] ]);
364	}
365else {
366	print &ui_form_end([ [ undef, $text{'save'} ],
367			     [ 'clone', $text{'edit_clone'} ],
368			     [ 'delete', $text{'delete'} ] ]);
369	}
370
371&ui_print_footer("index.cgi?version=${ipvx_arg}", $text{'index_return'});
372
373# print_mode(name, &value, [yes-option, no-option], [no-no-option])
374sub print_mode
375{
376local ($name, $value, $yes_opt, $no_opt, $no_no_opt) = @_;
377local $m = !$value ? 0 :
378	   $value->[0] eq "!" ? 2 : 1;
379return &ui_select($name."_mode", $m,
380	[ [ 0, "&lt;$text{'edit_ignore'}&gt;" ],
381	  [ 1, $yes_opt || $text{'edit_is'} ],
382	  !$no_no_opt || $m == 2 ? ( [ 2, $no_opt || $text{'edit_not'} ] )
383				 : ( ) ]);
384}
385
386# port_input(name, value)
387sub port_input
388{
389local ($name, $value) = @_;
390local ($s, $e, $p);
391if ($value =~ /^(\d*):(\d*)$/) {
392	$s = $1; $e = $2;
393	}
394else {
395	$p = $value || "";
396	}
397return &ui_radio($name."_type", defined($p) ? 0 : 1,
398		 [ [ 0, $text{'edit_port0'}." ".
399			&ui_textbox($name, $p, 15) ],
400		   [ 1, &text('edit_port1',
401			      &ui_textbox($name."_from", $s, 6),
402			      &ui_textbox($name."_to", $e, 6)) ] ]);
403}
404
405# tcpflag_input(name, value)
406sub tcpflag_input
407{
408local ($name, $value) = @_;
409local %flags = map { $_, 1 } split(/,/, $value);
410local $f;
411local $rv = "<font size=-1>\n";
412foreach $f ('SYN', 'ACK', 'FIN', 'RST', 'URG', 'PSH') {
413	$rv .= &ui_checkbox($name, $f, "<tt>$f</tt>",
414			    $flags{$f} || $flags{'ALL'})."\n";
415	}
416$rv .= "</font>\n";
417return $rv;
418}
419
420# icmptype_input(name, value, [&types])
421sub icmptype_input
422{
423local ($name, $value, $types) = @_;
424local ($started, @types, $major, $minor);
425$major = -1;
426if ($types) {
427	@types = @$types;
428	}
429else {
430	open(IPTABLES, "ip${ipvx}tables -p icmp${ipvx_icmp} -h 2>/dev/null |");
431	while(<IPTABLES>) {
432		if (/valid\s+icmp\s+types:/i) {
433			$started = 1;
434			}
435		elsif (!/\S/) {
436			$started = 0;
437			}
438		elsif ($started && /^\s*(\S+)/) {
439			push(@types, $1);
440			}
441		}
442	close(IPTABLES);
443	}
444if (@types && $value !~ /^\d+$/ && $value !~ /^\d+\/\d+$/) {
445	return &ui_select($name, $value, \@types);
446	}
447else {
448	return &ui_textbox($name, $value, 6);
449	}
450}
451
452# protocol_input(name, value)
453sub protocol_input
454{
455local ($name, $value) = @_;
456local @stdprotos = ( 'tcp', 'udp', "icmp${ipvx_icmp}", undef );
457$value ||= "tcp";
458local @otherprotos;
459open(PROTOS, "</etc/protocols");
460while(<PROTOS>) {
461	s/\r|\n//g;
462	s/#.*$//;
463	push(@otherprotos, $1) if (/^(\S+)\s+(\d+)/);
464	}
465close(PROTOS);
466@otherprotos = sort { lc($a) cmp lc($b) } @otherprotos;
467local $p;
468local @allprotos = &unique(@stdprotos, @otherprotos);
469local $found = &indexof($value, @allprotos) >= 0;
470return &ui_select($name, $found ? $value : "",
471	[ (map { [ $_, uc($_) || "-------" ] } @allprotos),
472	  [ '', $text{'edit_oifc'} ] ])." ".
473       &ui_textbox($name."_other", $found ? undef : $value, 5);
474}
475
476# tos_input(name, value)
477sub tos_input
478{
479local ($name, $value) = @_;
480local ($started, @opts);
481open(IPTABLES, "ip${ipvx}tables -m tos -h 2>/dev/null |");
482while(<IPTABLES>) {
483	if (/TOS.*options:/i) {
484		$started = 1;
485		}
486	elsif ($started && /^\s+(\S+)\s+(\d+)\s+\((0x[0-9a-f]+)\)/i) {
487		push(@opts, [ $1, $3 ]);
488		}
489	}
490close(IPTABLES);
491if (@opts) {
492	return &ui_select($name, $value,
493		[ map { [ $o->[0], "$o->[0] ($o->[1])" ] } @opts ]);
494	}
495else {
496	return &ui_textbox($name, $value, 20);
497	}
498}
499
500