Handle hostnames with upper-case letters
[webmin.git] / ipfw / save_rule.cgi
1 #!/usr/local/bin/perl
2 # Create, update or delete a firewall rule
3
4 require './ipfw-lib.pl';
5 &ReadParse();
6 &error_setup($text{'save_err'});
7
8 $rules = &get_config();
9 if ($in{'new'}) {
10         # Find the last editable rule
11         if ($rules->[@$rules-1]->{'num'} == 65535 &&
12             @$rules > 1) {
13                 $lastidx = $rules->[@$rules-2]->{'index'};
14                 }
15         else {
16                 $lastidx = $rules->[@$rules-1]->{'index'};
17                 }
18
19         # Work out where to insert, and what number to use
20         if ($in{'before'} ne '') {
21                 # Adding before some rule
22                 local $pn = $in{'before'} == 0 ? 0 :
23                             $rules->[$in{'before'}-1]->{'num'};
24                 $rule = { 'num' => ($rules->[$in{'before'}]->{'num'}+$pn)/2 };
25                 splice(@$rules, $in{'before'}, 0, $rule);
26                 }
27         elsif ($in{'after'} ne '') {
28                 # Adding after some rule
29                 local $nn = $in{'after'} == $lastidx ?
30                                 $rules->[$in{'after'}]->{'num'}+200 :
31                                 $rules->[$in{'after'}+1]->{'num'};
32                 $rule = { 'num' => ($rules->[$in{'after'}]->{'num'}+$nn)/2 };
33                 splice(@$rules, $in{'after'}+1, 0, $rule);
34                 }
35         elsif (!$in{'num_def'}) {
36                 # At specified number
37                 $in{'num'} =~ /^\d+$/ && $in{'num'} >= 0 && $in{'num'} < 65536
38                         || &error($text{'save_enum'});
39                 $rule = { 'num' => $in{'num'} };
40                 my $found = 0;
41                 for(my $i=0; $i<@$rules; $i++) {
42                         if ($rules->[$i]->{'num'} >= $in{'num'}) {
43                                 splice(@$rules, $i, 0, $rule);
44                                 $found++;
45                                 last;
46                                 }
47                         }
48                 push(@$rules, $rule) if (!$found);
49                 }
50         elsif (!@$rules) {
51                 # First rule
52                 $rule = { 'num' => '00100' };
53                 push(@$rules, $rule);
54                 }
55         else {
56                 # At end or before last deny-all rule
57                 $rule = { 'num' => $rules->[$lastidx]->{'num'}+100 };
58                 splice(@$rules, $lastidx+1, 0, $rule);
59                 }
60         $rule->{'num'} = sprintf "%5.5d", $rule->{'num'};
61         }
62 else {
63         $rule = $rules->[$in{'idx'}];
64         delete($rule->{'text'});
65         }
66
67 if ($in{'delete'}) {
68         # Just remove this rule
69         splice(@$rules, $in{'idx'}, 1);
70         }
71 else {
72         # Validate inputs and contruct the rule object
73         $in{'cmt'} =~ s/\r//g;
74         $rule->{'cmt'} = $in{'cmt'};
75
76         # Parse rule action and arg
77         $rule->{'action'} = $in{'action'};
78         if ($in{'action'} eq "skipto") {
79                 $in{'action_skipto'} =~ /^\d+$/ ||
80                         &error($text{'save_eskipto'});
81                 $rule->{'aarg'} = $in{'action_skipto'};
82                 }
83         elsif ($in{'action'} eq "fwd") {
84                 &check_ipaddress($in{'action_fwdip'}) ||
85                         &error($text{'save_efwdip'});
86                 if ($in{'action_fwdport'} eq "") {
87                         $rule->{'aarg'} = $in{'action_fwdip'};
88                         }
89                 else {
90                         $in{'action_fwdport'} =~ /^\d+$/ ||
91                                 &error($text{'save_efwdport'});
92                         $rule->{'aarg'} = $in{'action_fwdip'}.",".
93                                           $in{'action_fwdport'};
94                         }
95                 }
96         elsif ($in{'action'} eq "divert" || $in{'action'} eq "pipe" ||
97                $in{'action'} eq "queue" || $in{'action'} eq "tee") {
98                 $in{'action_port'} =~ /^\d+$/ ||
99                         &error($text{'save_eteeport'});
100                 $rule->{'aarg'} = $in{'action_port'};
101                 }
102         elsif ($in{'action'} eq "unreach") {
103                 $rule->{'aarg'} = $in{'action_unreach'};
104                 }
105         else {
106                 delete($rule->{'aarg'});
107                 }
108
109         # Parse protocol
110         if ($in{'proto_orblock'}) {
111                 $rule->{'proto'} = &parse_orblock("proto");
112                 }
113         else {
114                 $rule->{'proto'} = $in{'proto'};
115                 }
116
117         # Parse in/out option
118         delete($rule->{'in'});
119         delete($rule->{'out'});
120         delete($rule->{'in_not'});
121         delete($rule->{'out_not'});
122         if ($in{'inout'} == 1) {
123                 $rule->{'in'} = 1;
124                 }
125         elsif ($in{'inout'} == 2) {
126                 $rule->{'out'} = 1;
127                 }
128
129         # Parse via interface
130         $rule->{'via'} = &parse_interface("via");
131
132         # Parse logging level
133         if ($in{'log'}) {
134                 $rule->{'log'} = 1;
135                 if ($in{'logamount'} ne "") {
136                         $in{'logamount'} =~ /^\d+$/ ||
137                                 &error($text{'save_elogamount'});
138                         $rule->{'logamount'} = $in{'logamount'};
139                         }
140                 else {
141                         delete($rule->{'logamount'});
142                         }
143                 }
144         else {
145                 $rule->{'log'} = 0;
146                 }
147
148         # Parse source and destination
149         foreach $s ("from", "to") {
150                 # IP address
151                 if ($in{$s."_orblock"}) {
152                         $rule->{$s} = &parse_orblock($s);
153                         }
154                 elsif ($in{$s."_mode"} == 0) {
155                         $rule->{$s} = "any";
156                         }
157                 elsif ($in{$s."_mode"} == 1) {
158                         $rule->{$s} = "me";
159                         }
160                 else {
161                         &to_ipaddress($in{$s}) ||
162                             ($in{$s} =~ /^([0-9\.]+)\/(\d+)$/ &&
163                              &check_ipaddress("$1")) ||
164                             ($in{$s} =~ /^([0-9\.]+)\/(\d+)\{([0-9,]+)\}$/ &&
165                              &check_ipaddress("$1") &&
166                              $ipfw_version >= 2) ||
167                                 &error($text{'save_e'.$s});
168                         $rule->{$s} = $in{$s};
169                         }
170
171                 # Port numbers
172                 if ($in{$s."_ports_orblock"}) {
173                         # XXX could be optional?
174                         $rule->{$s."_ports"} = &parse_orblock($s."_ports");
175                         }
176                 elsif ($in{$s."_ports_mode"} == 0) {
177                         delete($rule->{$s."_ports"});
178                         }
179                 else {
180                         local $p = $rule->{'proto'};
181                         $p eq "tcp" || $p eq "udp" || $p eq "ip" ||
182                             $ipfw_version >= 2 ||
183                                 &error($text{'save_eportsproto'.$s});
184                         $in{$s."_ports"} =~ /^\d+$/ ||
185                           getservbyname($in{$s."_ports"}, $p) ||
186                           $in{$s."_ports"} =~ /^\d+\-\d+$/ ||
187                           ($in{$s."_ports"} =~ /^([a-z0-9]+)\-([a-z0-9]+)$/i &&
188                            getservbyname($1, $p) && getservbyname($2, $p)) ||
189                           $in{$s."_ports"} =~ /^([a-z0-9]+)(,[a-z0-9]+)*$/ ||
190                           ($in{$s."_ports"} =~ /^([a-z0-9]+|([a-z0-9]+)\-([a-z0-9]+))(,[a-z0-9]+|,([a-z0-9]+)\-([a-z0-9]+))*$/ &&
191                            $ipfw_version >= 2) ||
192                                 &error($text{'save_eports'.$s});
193                         $rule->{$s."_ports"} = $in{$s."_ports"};
194                         $rule->{$s."_ports_not"} = $in{$s."_ports_not"}
195                                 if ($ipfw_version >= 2);
196                         }
197                 }
198         $rule->{'xmit'} = &parse_interface("xmit");
199         $rule->{'recv'} = &parse_interface("recv");
200
201         # XXX multiple options
202
203         # Parse various options
204         &parse_yes_no_ignored("established");
205         &parse_yes_no_ignored("keep-state");
206         &parse_yes_no_ignored("bridged");
207         &parse_yes_no_ignored("frag");
208         &parse_yes_no_ignored("setup");
209
210         # Parse MAC address
211         if ($ipfw_version >= 2) {
212                 if ($in{'mac1_def'} && $in{'mac2_def'}) {
213                         delete($rule->{'mac'});
214                         }
215                 else {
216                         local @mac;
217                         if ($in{'mac2_def'}) {
218                                 push(@mac, "any");
219                                 }
220                         else {
221                                 $in{'mac2'} =~ /^[0-9a-f]{2}(:[0-9a-f]{2}){5}(\/\d+)?$/ || &error($text{'save_emac2'});
222                                 push(@mac, $in{'mac2'});
223                                 }
224                         if ($in{'mac1_def'}) {
225                                 push(@mac, "any");
226                                 }
227                         else {
228                                 $in{'mac1'} =~ /^[0-9a-f]{2}(:[0-9a-f]{2}){5}(\/\d+)?$/ || &error($text{'save_emac1'});
229                                 push(@mac, $in{'mac1'});
230                                 }
231                         $rule->{'mac'} = \@mac;
232                         }
233                 }
234
235         # Parse UID and GID
236         if ($in{'uid_def'}) {
237                 delete($rule->{'uid'});
238                 }
239         elsif ($in{'uid'} =~ /^#(\d+)$/) {
240                 $rule->{'uid'} = $1;
241                 }
242         else {
243                 defined($rule->{'uid'} = getpwnam($in{'uid'})) ||
244                         &error($text{'save_euid'});
245                 }
246         if ($in{'gid_def'}) {
247                 delete($rule->{'gid'});
248                 }
249         elsif ($in{'gid'} =~ /^#(\d+)$/) {
250                 $rule->{'gid'} = $1;
251                 }
252         else {
253                 defined($rule->{'gid'} = getgrnam($in{'gid'})) ||
254                         &error($text{'save_egid'});
255                 }
256
257         # Parse ICMP types
258         if ($in{'icmptypes'}) {
259                 $rule->{'proto'} eq 'icmp' || &error($text{'save_eicmptypes'});
260                 $rule->{'icmptypes'} = join(",", split(/\0/, $in{'icmptypes'}));
261                 }
262         else {
263                 delete($rule->{'icmptypes'});
264                 }
265
266         # Parse tcp flags
267         if ($in{'tcpflags'}) {
268                 $rule->{'proto'} eq 'tcp' || &error($text{'save_etcpflags'});
269                 $rule->{'tcpflags'} = join(",", split(/\0/, $in{'tcpflags'}));
270                 }
271         else {
272                 delete($rule->{'tcpflags'});
273                 }
274
275         # Parse limit directive
276         if ($in{'limit'}) {
277                 $in{'limit2'} =~ /^\d+$/ || &error($text{'save_elimit'});
278                 $rule->{'limit'} = [ $in{'limit'}, $in{'limit2'} ];
279                 }
280         else {
281                 delete($rule->{'limit'});
282                 }
283
284         # Parse dst-port and src-port directive
285         foreach $ds ('dst', 'src') {
286                 if (!$in{$ds.'port_def'}) {
287                         local @dstports = split(/[ ,]+/, $in{$ds.'port'});
288                         foreach $p (@dstports) {
289                                 &valid_port($p, $rule->{'proto'}) ||
290                                         &error($text{'save_e'.$ds.'port'});
291                                 }
292                         $rule->{$ds.'-port'} = \@dstports;
293                         }
294                 else {
295                         delete($rule->{$ds.'-port'});
296                         }
297                 }
298         }
299
300 # Save all rules
301 &lock_file($ipfw_file);
302 &save_config($rules);
303 &unlock_file($ipfw_file);
304 &copy_to_cluster();
305 &webmin_log($in{'delete'} ? "delete" : $in{'new'} ? "create" : "modify",
306             "rule", $rule->{'action'}, $rule);
307 &redirect("");
308
309 # parse_interface(name)
310 sub parse_interface
311 {
312 local $iface = $in{$_[0]} eq "other" ? $in{$_[0]."_other"} : $in{$_[0]};
313 return undef if (!$iface);
314 $iface =~ /^\S+$/ || &error($text{'save_e'.$_[0]});
315 return $iface;
316 }
317
318 # parse_orblock(name)
319 sub parse_orblock
320 {
321 $in{$_[0]} =~ /\S/ || &error(&text('save_eorblock'.$_[0])); 
322 return [ split(/\s+/, $in{$_[0]}) ];
323 }
324
325 # parse_yes_no_ignored(name)
326 sub parse_yes_no_ignored
327 {
328 if ($in{$_[0]} == 0) {
329         delete($rule->{$_[0]});
330         }
331 elsif ($in{$_[0]} == 1) {
332         $rule->{$_[0]} = 1;
333         $rule->{$_[0]."_not"} = 0;
334         }
335 elsif ($in{$_[0]} == 2) {
336         $rule->{$_[0]} = 1;
337         $rule->{$_[0]."_not"} = 1;
338         }
339 }
340