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