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