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©_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