| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # | 
| 2 |  |  |  |  |  |  | ############################################################################## | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # File: IPTables::ChainMgr.pm | 
| 5 |  |  |  |  |  |  | # | 
| 6 |  |  |  |  |  |  | # Purpose: Perl interface to add and delete rules to an iptables chain.  The | 
| 7 |  |  |  |  |  |  | #          most common application of this module is to create a custom chain | 
| 8 |  |  |  |  |  |  | #          and then add blocking rules to it.  Rule additions are (mostly) | 
| 9 |  |  |  |  |  |  | #          guaranteed to be unique. | 
| 10 |  |  |  |  |  |  | # | 
| 11 |  |  |  |  |  |  | # Author: Michael Rash (mbr@cipherdyne.org) | 
| 12 |  |  |  |  |  |  | # | 
| 13 |  |  |  |  |  |  | # Version: 1.3 | 
| 14 |  |  |  |  |  |  | # | 
| 15 |  |  |  |  |  |  | ############################################################################## | 
| 16 |  |  |  |  |  |  | # | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | package IPTables::ChainMgr; | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 1 |  |  | 1 |  | 7995 | use 5.006; | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 45 |  | 
| 21 | 1 |  |  | 1 |  | 660 | use POSIX ':sys_wait_h'; | 
|  | 1 |  |  |  |  | 6370 |  | 
|  | 1 |  |  |  |  | 5 |  | 
| 22 | 1 |  |  | 1 |  | 1288 | use Carp; | 
|  | 1 |  |  |  |  | 8 |  | 
|  | 1 |  |  |  |  | 61 |  | 
| 23 | 1 |  |  | 1 |  | 770 | use IPTables::Parse; | 
|  | 1 |  |  |  |  | 6127 |  | 
|  | 1 |  |  |  |  | 43 |  | 
| 24 | 1 |  |  | 1 |  | 735 | use NetAddr::IP; | 
|  | 1 |  |  |  |  | 64818 |  | 
|  | 1 |  |  |  |  | 6 |  | 
| 25 | 1 |  |  | 1 |  | 145 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 41 |  | 
| 26 | 1 |  |  | 1 |  | 6 | use warnings; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 39 |  | 
| 27 | 1 |  |  | 1 |  | 5 | use vars qw($VERSION); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 4480 |  | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | $VERSION = '1.3'; | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | sub new() { | 
| 32 | 0 |  |  | 0 | 0 |  | my $class = shift; | 
| 33 | 0 |  |  |  |  |  | my %args  = @_; | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 0 |  |  |  |  |  | my $self = {}; | 
| 36 |  |  |  |  |  |  |  | 
| 37 | 0 |  |  |  |  |  | $self->{'parse_obj'} = IPTables::Parse->new(%args); | 
| 38 |  |  |  |  |  |  |  | 
| 39 | 0 |  |  |  |  |  | for my $key ('_cmd', | 
| 40 |  |  |  |  |  |  | '_ipt_bin_name', | 
| 41 |  |  |  |  |  |  | '_iptables', | 
| 42 |  |  |  |  |  |  | '_firewall_cmd', | 
| 43 |  |  |  |  |  |  | '_fwd_args', | 
| 44 |  |  |  |  |  |  | '_ipv6', | 
| 45 |  |  |  |  |  |  | '_iptout', | 
| 46 |  |  |  |  |  |  | '_ipterr', | 
| 47 |  |  |  |  |  |  | '_ipt_alarm', | 
| 48 |  |  |  |  |  |  | '_debug', | 
| 49 |  |  |  |  |  |  | '_verbose', | 
| 50 |  |  |  |  |  |  | '_ipt_exec_style', | 
| 51 |  |  |  |  |  |  | '_ipt_exec_sleep', | 
| 52 |  |  |  |  |  |  | '_sigchld_handler', | 
| 53 |  |  |  |  |  |  | ) { | 
| 54 | 0 |  |  |  |  |  | $self->{$key} = $self->{'parse_obj'}->{$key}; | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  |  | 
| 57 | 0 |  |  |  |  |  | bless $self, $class; | 
| 58 |  |  |  |  |  |  | } | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | sub chain_exists() { | 
| 61 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 62 | 0 |  | 0 |  |  |  | my $table = shift || croak '[*] Must specify a table, e.g. "filter".'; | 
| 63 | 0 |  | 0 |  |  |  | my $chain = shift || croak '[*] Must specify a chain to check.'; | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | ### see if the chain exists | 
| 66 | 0 |  |  |  |  |  | return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -v -n -L $chain"); | 
| 67 |  |  |  |  |  |  | } | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | sub create_chain() { | 
| 70 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 71 | 0 |  | 0 |  |  |  | my $table = shift || croak '[*] Must specify a table, e.g. "filter".'; | 
| 72 | 0 |  | 0 |  |  |  | my $chain = shift || croak '[*] Must specify a chain to create.'; | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | ### see if the chain exists first | 
| 75 | 0 |  |  |  |  |  | my ($rv, $out_ar, $err_ar) = $self->chain_exists($table, $chain); | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | ### the chain already exists | 
| 78 | 0 | 0 |  |  |  |  | return 1, $out_ar, $err_ar if $rv; | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | ### create the chain | 
| 81 | 0 |  |  |  |  |  | return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -N $chain"); | 
| 82 |  |  |  |  |  |  | } | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | sub flush_chain() { | 
| 85 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 86 | 0 |  | 0 |  |  |  | my $table = shift || croak '[*] Must specify a table, e.g. "filter".'; | 
| 87 | 0 |  | 0 |  |  |  | my $chain = shift || croak '[*] Must specify a chain.'; | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | ### flush the chain | 
| 90 | 0 |  |  |  |  |  | return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -F $chain"); | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | sub delete_chain() { | 
| 94 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 95 | 0 |  | 0 |  |  |  | my $table = shift || croak '[*] Must specify a table, e.g. "filter".'; | 
| 96 | 0 |  | 0 |  |  |  | my $jump_from_chain = shift || | 
| 97 |  |  |  |  |  |  | croak '[*] Must specify a chain from which ', | 
| 98 |  |  |  |  |  |  | 'packets were jumped to this chain'; | 
| 99 | 0 |  | 0 |  |  |  | my $del_chain = shift || croak '[*] Must specify a chain to delete.'; | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | ### see if the chain exists first | 
| 102 | 0 |  |  |  |  |  | my ($rv, $out_ar, $err_ar) = $self->chain_exists($table, $del_chain); | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | ### return true if the chain doesn't exist (it is not an error condition) | 
| 105 | 0 | 0 |  |  |  |  | return 1, $out_ar, $err_ar unless $rv; | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | ### flush the chain | 
| 108 | 0 |  |  |  |  |  | ($rv, $out_ar, $err_ar) | 
| 109 |  |  |  |  |  |  | = $self->flush_chain($table, $del_chain); | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | ### could not flush the chain | 
| 112 | 0 | 0 |  |  |  |  | return 0, $out_ar, $err_ar unless $rv; | 
| 113 |  |  |  |  |  |  |  | 
| 114 | 0 |  |  |  |  |  | my $ip_any_net = '0.0.0.0/0'; | 
| 115 | 0 | 0 |  |  |  |  | $ip_any_net = '::/0' if $self->{'_ipv6'}; | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | ### find and delete jump rules to this chain (we can't delete | 
| 118 |  |  |  |  |  |  | ### the chain until there are no references to it) | 
| 119 | 0 |  |  |  |  |  | my ($rulenum, $num_chain_rules) | 
| 120 |  |  |  |  |  |  | = $self->find_ip_rule($ip_any_net, $ip_any_net, | 
| 121 |  |  |  |  |  |  | $table, $jump_from_chain, $del_chain, {}); | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 0 | 0 |  |  |  |  | if ($rulenum) { | 
| 124 | 0 |  |  |  |  |  | $self->run_ipt_cmd( | 
| 125 |  |  |  |  |  |  | "$self->{'_cmd'} -t $table -D $jump_from_chain $rulenum"); | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | ### note that we try to delete the chain now regardless | 
| 129 |  |  |  |  |  |  | ### of whether their were jump rules above (should probably | 
| 130 |  |  |  |  |  |  | ### parse for the "0 references" under the -nL  output). | 
| 131 | 0 |  |  |  |  |  | return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -X $del_chain"); | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | sub set_chain_policy() { | 
| 135 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 136 | 0 |  | 0 |  |  |  | my $table = shift || croak '[*] Must specify a table, e.g. "filter".'; | 
| 137 | 0 |  | 0 |  |  |  | my $chain = shift || croak '[*] Must specify a chain.'; | 
| 138 | 0 |  | 0 |  |  |  | my $target  = shift || croak qq|[-] Must specify | . | 
| 139 |  |  |  |  |  |  | qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|; | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | ### set the chain policy: note that $chain must be a built-in chain | 
| 142 | 0 |  |  |  |  |  | return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -P $chain $target"); | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | sub append_ip_rule() { | 
| 146 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 147 | 0 |  | 0 |  |  |  | my $src = shift || croak '[-] Must specify a src address/network.'; | 
| 148 | 0 |  | 0 |  |  |  | my $dst = shift || croak '[-] Must specify a dst address/network.'; | 
| 149 | 0 |  | 0 |  |  |  | my $table   = shift || croak '[-] Must specify a table, e.g. "filter".'; | 
| 150 | 0 |  | 0 |  |  |  | my $chain   = shift || croak '[-] Must specify a chain.'; | 
| 151 | 0 |  | 0 |  |  |  | my $target  = shift || croak qq|[-] Must specify | . | 
| 152 |  |  |  |  |  |  | qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|; | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | ### optionally add port numbers and protocols, etc. | 
| 155 | 0 |  | 0 |  |  |  | my $extended_hr = shift || {}; | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | ### -1 for append | 
| 158 | 0 |  |  |  |  |  | return $self->add_ip_rule($src, $dst, -1, $table, | 
| 159 |  |  |  |  |  |  | $chain, $target, $extended_hr); | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | sub add_ip_rule() { | 
| 163 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 164 | 0 |  | 0 |  |  |  | my $src = shift || croak '[-] Must specify a src address/network.'; | 
| 165 | 0 |  | 0 |  |  |  | my $dst = shift || croak '[-] Must specify a dst address/network.'; | 
| 166 | 0 | 0 |  |  |  |  | (my $rulenum = shift) >= -1 || croak '[-] Must specify insert rule number, or -1 for append.'; | 
| 167 | 0 |  | 0 |  |  |  | my $table   = shift || croak '[-] Must specify a table, e.g. "filter".'; | 
| 168 | 0 |  | 0 |  |  |  | my $chain   = shift || croak '[-] Must specify a chain.'; | 
| 169 | 0 |  | 0 |  |  |  | my $target  = shift || | 
| 170 |  |  |  |  |  |  | croak qq|[-] Must specify $self->{'_ipt_bin_name'} | . | 
| 171 |  |  |  |  |  |  | qq|target, e.g. "DROP"|; | 
| 172 |  |  |  |  |  |  | ### optionally add port numbers and protocols, etc. | 
| 173 | 0 |  | 0 |  |  |  | my $extended_hr = shift || {}; | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | ### normalize src/dst if necessary; this is because iptables | 
| 176 |  |  |  |  |  |  | ### always reports the network address for subnets | 
| 177 | 0 |  |  |  |  |  | my $normalized_src = $self->normalize_net($src); | 
| 178 | 0 |  |  |  |  |  | my $normalized_dst = $self->normalize_net($dst); | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | ### first check to see if this rule already exists | 
| 181 | 0 |  |  |  |  |  | my ($rule_position, $num_chain_rules) | 
| 182 |  |  |  |  |  |  | = $self->find_ip_rule($normalized_src, $normalized_dst, $table, | 
| 183 |  |  |  |  |  |  | $chain, $target, $extended_hr); | 
| 184 |  |  |  |  |  |  |  | 
| 185 | 0 | 0 |  |  |  |  | if ($rule_position) { | 
| 186 | 0 |  |  |  |  |  | my $msg = ''; | 
| 187 | 0 | 0 |  |  |  |  | if (keys %$extended_hr) { | 
| 188 | 0 |  |  |  |  |  | $msg = "Table: $table, chain: $chain, $normalized_src -> " . | 
| 189 |  |  |  |  |  |  | "$normalized_dst "; | 
| 190 | 0 |  |  |  |  |  | for my $key (keys %$extended_hr) { | 
| 191 | 0 | 0 |  |  |  |  | $msg .= "$key $extended_hr->{$key} " | 
| 192 |  |  |  |  |  |  | if defined $extended_hr->{$key}; | 
| 193 |  |  |  |  |  |  | } | 
| 194 | 0 |  |  |  |  |  | $msg .= 'rule already exists.'; | 
| 195 |  |  |  |  |  |  | } else { | 
| 196 | 0 |  |  |  |  |  | $msg = "Table: $table, chain: $chain, $normalized_src -> " . | 
| 197 |  |  |  |  |  |  | "$normalized_dst rule already exists."; | 
| 198 |  |  |  |  |  |  | } | 
| 199 | 0 |  |  |  |  |  | return 1, [$msg], []; | 
| 200 |  |  |  |  |  |  | } | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | ### we need to add the rule | 
| 203 | 0 |  |  |  |  |  | my $ipt_cmd = ''; | 
| 204 | 0 |  |  |  |  |  | my $msg     = ''; | 
| 205 | 0 |  |  |  |  |  | my $idx_err = ''; | 
| 206 |  |  |  |  |  |  |  | 
| 207 | 0 | 0 |  |  |  |  | if ($rulenum == 0) { | 
|  |  | 0 |  |  |  |  |  | 
| 208 | 0 |  |  |  |  |  | $ipt_cmd = "$self->{'_cmd'} -t $table -I $chain 1 "; | 
| 209 |  |  |  |  |  |  | } elsif ($rulenum < 0) { | 
| 210 |  |  |  |  |  |  | ### switch to append mode | 
| 211 | 0 |  |  |  |  |  | $ipt_cmd = "$self->{'_cmd'} -t $table -A $chain "; | 
| 212 |  |  |  |  |  |  | } else { | 
| 213 |  |  |  |  |  |  | ### check to see if the insertion index ($rulenum) is too big | 
| 214 | 0 | 0 |  |  |  |  | if ($rulenum > $num_chain_rules+1) { | 
| 215 | 0 | 0 |  |  |  |  | $idx_err = "Rule position $rulenum is past end of $chain " . | 
| 216 |  |  |  |  |  |  | "chain ($num_chain_rules rules), compensating." | 
| 217 |  |  |  |  |  |  | if $num_chain_rules > 0; | 
| 218 | 0 |  |  |  |  |  | $rulenum = $num_chain_rules + 1; | 
| 219 |  |  |  |  |  |  | } | 
| 220 | 0 |  |  |  |  |  | $ipt_cmd = "$self->{'_cmd'} -t $table -I $chain $rulenum "; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 | 0 | 0 |  |  |  |  | if (keys %$extended_hr) { | 
| 224 |  |  |  |  |  |  |  | 
| 225 | 0 |  |  |  |  |  | my ($ipt_tmp_str, $msg_tmp_str) = $self->build_ipt_matches( | 
| 226 |  |  |  |  |  |  | $extended_hr, $normalized_src, $normalized_dst); | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 0 |  |  |  |  |  | $msg = "Table: $table, chain: $chain, added $normalized_src " . | 
| 229 |  |  |  |  |  |  | "-> $normalized_dst "; | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | ### always add the target at the end | 
| 232 | 0 |  |  |  |  |  | $ipt_cmd .= "$ipt_tmp_str -j $target"; | 
| 233 |  |  |  |  |  |  |  | 
| 234 | 0 |  |  |  |  |  | $msg .= $msg_tmp_str; | 
| 235 | 0 |  |  |  |  |  | $msg =~ s/\s*$//; | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | } else { | 
| 238 | 0 |  |  |  |  |  | $ipt_cmd .= "-s $normalized_src -d $normalized_dst -j $target"; | 
| 239 | 0 |  |  |  |  |  | $msg = "Table: $table, chain: $chain, added $normalized_src " . | 
| 240 |  |  |  |  |  |  | "-> $normalized_dst"; | 
| 241 |  |  |  |  |  |  | } | 
| 242 | 0 |  |  |  |  |  | my ($rv, $out_ar, $err_ar) = $self->run_ipt_cmd($ipt_cmd); | 
| 243 | 0 | 0 |  |  |  |  | if ($rv) { | 
| 244 | 0 | 0 |  |  |  |  | push @$out_ar, $msg if $msg; | 
| 245 |  |  |  |  |  |  | } | 
| 246 | 0 | 0 |  |  |  |  | push @$err_ar, $idx_err if $idx_err; | 
| 247 | 0 |  |  |  |  |  | return $rv, $out_ar, $err_ar; | 
| 248 |  |  |  |  |  |  | } | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | sub build_ipt_matches() { | 
| 251 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 252 | 0 |  |  |  |  |  | my $extended_hr = shift; | 
| 253 | 0 |  | 0 |  |  |  | my $normalized_src = shift || ''; | 
| 254 | 0 |  | 0 |  |  |  | my $normalized_dst = shift || ''; | 
| 255 |  |  |  |  |  |  |  | 
| 256 | 0 |  |  |  |  |  | my $ipt_matches = ''; | 
| 257 | 0 |  |  |  |  |  | my $msg = ''; | 
| 258 |  |  |  |  |  |  |  | 
| 259 | 0 | 0 |  |  |  |  | if ($IPTables::Parse::VERSION > 1.1) { | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | ### src and dst | 
| 262 | 0 | 0 |  |  |  |  | if ($normalized_src ne '') { | 
| 263 | 0 |  |  |  |  |  | $ipt_matches .= $self->{'parse_obj'}-> | 
| 264 |  |  |  |  |  |  | {'parse_keys'}->{'regular'}->{'src'}->{'ipt_match'} . | 
| 265 |  |  |  |  |  |  | " $normalized_src "; | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 0 | 0 |  |  |  |  | if ($normalized_src ne '') { | 
| 269 | 0 |  |  |  |  |  | $ipt_matches .= $self->{'parse_obj'}-> | 
| 270 |  |  |  |  |  |  | {'parse_keys'}->{'regular'}->{'dst'}->{'ipt_match'} . | 
| 271 |  |  |  |  |  |  | " $normalized_dst "; | 
| 272 |  |  |  |  |  |  | } | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | ### handle 'regular' keys first | 
| 275 | 0 |  |  |  |  |  | for my $key (keys %$extended_hr) { | 
| 276 | 0 | 0 |  |  |  |  | if (defined $self->{'parse_obj'}->{'parse_keys'}->{'regular'}->{$key}) { | 
| 277 | 0 |  |  |  |  |  | $ipt_matches .= $self->{'parse_obj'}-> | 
| 278 |  |  |  |  |  |  | {'parse_keys'}->{'regular'}->{$key}->{'ipt_match'} . | 
| 279 |  |  |  |  |  |  | " $extended_hr->{$key} "; | 
| 280 |  |  |  |  |  |  | } | 
| 281 |  |  |  |  |  |  | } | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | ### special case for port values (handle them now) | 
| 284 | 0 |  |  |  |  |  | for my $key (qw/sport s_dport dport d_port/) { | 
| 285 | 0 | 0 |  |  |  |  | next unless defined $extended_hr->{$key}; | 
| 286 | 0 | 0 |  |  |  |  | if ($extended_hr->{$key}) { | 
| 287 | 0 |  |  |  |  |  | $ipt_matches .= $self->{'parse_obj'}-> | 
| 288 |  |  |  |  |  |  | {'parse_keys'}->{'extended'}->{$key}->{'ipt_match'} . | 
| 289 |  |  |  |  |  |  | qq| $extended_hr->{$key} |; | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  | } | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  | ### now handle 'match' keys | 
| 294 | 0 |  |  |  |  |  | for my $key (keys %$extended_hr) { | 
| 295 | 0 |  |  |  |  |  | my $parse_hr = $self->{'parse_obj'}->{'parse_keys'}->{'extended'}; | 
| 296 | 0 | 0 |  |  |  |  | if (defined $parse_hr->{$key}) { | 
| 297 | 0 | 0 | 0 |  |  |  | next if $key =~ /s_?port$/ or $key =~ /d_?port$/; | 
| 298 | 0 | 0 | 0 |  |  |  | if (defined $parse_hr->{$key}->{'use_quotes'} | 
| 299 |  |  |  |  |  |  | and $parse_hr->{$key}->{'use_quotes'}) { | 
| 300 | 0 |  |  |  |  |  | $ipt_matches .= "$parse_hr->{$key}->{'ipt_match'} " . | 
| 301 |  |  |  |  |  |  | qq|"$extended_hr->{$key}" |; | 
| 302 |  |  |  |  |  |  | } else { | 
| 303 | 0 |  |  |  |  |  | $ipt_matches .= "$parse_hr->{$key}->{'ipt_match'} " . | 
| 304 |  |  |  |  |  |  | "$extended_hr->{$key} "; | 
| 305 |  |  |  |  |  |  | } | 
| 306 |  |  |  |  |  |  | } | 
| 307 |  |  |  |  |  |  | } | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | } else { | 
| 310 | 0 | 0 |  |  |  |  | $ipt_matches .= "-p $extended_hr->{'protocol'} " | 
| 311 |  |  |  |  |  |  | if defined $extended_hr->{'protocol'}; | 
| 312 | 0 |  |  |  |  |  | $ipt_matches .= "-s $normalized_src "; | 
| 313 | 0 | 0 |  |  |  |  | $ipt_matches .= "--sport $extended_hr->{'s_port'} " | 
| 314 |  |  |  |  |  |  | if defined $extended_hr->{'s_port'}; | 
| 315 | 0 |  |  |  |  |  | $ipt_matches .= "-d $normalized_dst "; | 
| 316 | 0 | 0 |  |  |  |  | $ipt_matches .= "--dport $extended_hr->{'d_port'} " | 
| 317 |  |  |  |  |  |  | if defined $extended_hr->{'d_port'}; | 
| 318 | 0 | 0 |  |  |  |  | $ipt_matches .= "-m mac --mac-source $extended_hr->{'mac_source'} " | 
| 319 |  |  |  |  |  |  | if defined $extended_hr->{'mac_source'}; | 
| 320 | 0 | 0 |  |  |  |  | $ipt_matches .= "-m state --state $extended_hr->{'state'} " | 
| 321 |  |  |  |  |  |  | if defined $extended_hr->{'state'}; | 
| 322 | 0 | 0 |  |  |  |  | $ipt_matches .= "-m conntrack --ctstate $extended_hr->{'ctstate'} " | 
| 323 |  |  |  |  |  |  | if defined $extended_hr->{'ctstate'}; | 
| 324 |  |  |  |  |  |  |  | 
| 325 | 0 |  |  |  |  |  | for my $key (keys %$extended_hr) { | 
| 326 | 0 | 0 |  |  |  |  | $msg .= "$key $extended_hr->{$key} " | 
| 327 |  |  |  |  |  |  | if defined $extended_hr->{$key}; | 
| 328 |  |  |  |  |  |  | } | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | ### for NAT | 
| 331 | 0 | 0 | 0 |  |  |  | if (defined $extended_hr->{'to_ip'} and | 
| 332 |  |  |  |  |  |  | defined $extended_hr->{'to_port'}) { | 
| 333 | 0 |  |  |  |  |  | $ipt_matches .= " --to $extended_hr->{'to_ip'}:" . | 
| 334 |  |  |  |  |  |  | "$extended_hr->{'to_port'}"; | 
| 335 | 0 |  |  |  |  |  | $msg .= "$extended_hr->{'to_ip'}:$extended_hr->{'to_port'}"; | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  | } | 
| 338 |  |  |  |  |  |  |  | 
| 339 | 0 |  |  |  |  |  | $ipt_matches =~ s/\s*$//; | 
| 340 |  |  |  |  |  |  |  | 
| 341 | 0 |  |  |  |  |  | return ($ipt_matches, $msg); | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | sub delete_ip_rule() { | 
| 345 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 346 | 0 |  | 0 |  |  |  | my $src = shift || croak '[-] Must specify a src address/network.'; | 
| 347 | 0 |  | 0 |  |  |  | my $dst = shift || croak '[-] Must specify a dst address/network.'; | 
| 348 | 0 |  | 0 |  |  |  | my $table  = shift || croak '[-] Must specify a table, e.g. "filter".'; | 
| 349 | 0 |  | 0 |  |  |  | my $chain  = shift || croak '[-] Must specify a chain.'; | 
| 350 | 0 |  | 0 |  |  |  | my $target = shift || croak qq|[-] Must specify | . | 
| 351 |  |  |  |  |  |  | qq|$self->{'_ipt_bin_name'} target, e.g. "DROP"|; | 
| 352 |  |  |  |  |  |  | ### optionally add port numbers and protocols, etc. | 
| 353 | 0 |  | 0 |  |  |  | my $extended_hr = shift || {}; | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | ### normalize src/dst if necessary; this is because iptables | 
| 356 |  |  |  |  |  |  | ### always reports network address for subnets | 
| 357 | 0 |  |  |  |  |  | my $normalized_src = $self->normalize_net($src); | 
| 358 | 0 |  |  |  |  |  | my $normalized_dst = $self->normalize_net($dst); | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | ### first check to see if this rule already exists | 
| 361 | 0 |  |  |  |  |  | my ($rulenum, $num_chain_rules) | 
| 362 |  |  |  |  |  |  | = $self->find_ip_rule($normalized_src, | 
| 363 |  |  |  |  |  |  | $normalized_dst, $table, $chain, $target, $extended_hr); | 
| 364 |  |  |  |  |  |  |  | 
| 365 | 0 | 0 |  |  |  |  | if ($rulenum) { | 
| 366 |  |  |  |  |  |  | ### we need to delete the rule | 
| 367 | 0 |  |  |  |  |  | return $self->run_ipt_cmd("$self->{'_cmd'} -t $table -D $chain $rulenum"); | 
| 368 |  |  |  |  |  |  | } | 
| 369 |  |  |  |  |  |  |  | 
| 370 | 0 |  |  |  |  |  | my $extended_msg = ''; | 
| 371 | 0 | 0 |  |  |  |  | if (keys %$extended_hr) { | 
| 372 | 0 |  |  |  |  |  | for my $key (keys %$extended_hr) { | 
| 373 | 0 | 0 |  |  |  |  | $extended_msg .= "$key: $extended_hr->{$key} " | 
| 374 |  |  |  |  |  |  | if defined $extended_hr->{$key}; | 
| 375 |  |  |  |  |  |  | } | 
| 376 |  |  |  |  |  |  | ### for NAT | 
| 377 | 0 | 0 | 0 |  |  |  | if (defined $extended_hr->{'to_ip'} and | 
| 378 |  |  |  |  |  |  | defined $extended_hr->{'to_port'}) { | 
| 379 | 0 |  |  |  |  |  | $extended_msg .= "$extended_hr->{'to_ip'}:" . | 
| 380 |  |  |  |  |  |  | "$extended_hr->{'to_port'}"; | 
| 381 |  |  |  |  |  |  | } | 
| 382 |  |  |  |  |  |  | } | 
| 383 | 0 |  |  |  |  |  | $extended_msg =~ s/\s*$//; | 
| 384 | 0 |  |  |  |  |  | return 0, [], ["Table: $table, chain: $chain, rule $normalized_src " . | 
| 385 |  |  |  |  |  |  | "-> $normalized_dst $extended_msg does not exist."]; | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | sub find_ip_rule() { | 
| 389 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 390 | 0 |  |  |  |  |  | my $debug   = $self->{'_debug'}; | 
| 391 | 0 |  |  |  |  |  | my $verbose = $self->{'_verbose'}; | 
| 392 | 0 |  | 0 |  |  |  | my $src   = shift || croak '[*] Must specify source address.'; | 
| 393 | 0 |  | 0 |  |  |  | my $dst   = shift || croak '[*] Must specify destination address.'; | 
| 394 | 0 |  | 0 |  |  |  | my $table = shift || croak qq|[*] Must specify $self->{'_ipt_bin_name'} table.|; | 
| 395 | 0 |  | 0 |  |  |  | my $chain = shift || croak qq|[*] Must specify $self->{'_ipt_bin_name'} chain.|; | 
| 396 | 0 |  | 0 |  |  |  | my $target = shift || croak qq|[*] Must specify | . | 
| 397 |  |  |  |  |  |  | qq|$self->{'_ipt_bin_name'} target (this may be a chain).|; | 
| 398 |  |  |  |  |  |  |  | 
| 399 |  |  |  |  |  |  | ### optionally add port numbers and protocols, etc. | 
| 400 | 0 |  | 0 |  |  |  | my $extended_hr = shift || {}; | 
| 401 |  |  |  |  |  |  |  | 
| 402 | 0 |  |  |  |  |  | my $fh = *STDERR; | 
| 403 | 0 | 0 |  |  |  |  | $fh = *STDOUT if $verbose; | 
| 404 |  |  |  |  |  |  |  | 
| 405 | 0 | 0 | 0 |  |  |  | if ($debug or $verbose) { | 
| 406 | 0 |  |  |  |  |  | print $fh localtime() . " [+] IPTables::Parse::VERSION ", | 
| 407 |  |  |  |  |  |  | "$IPTables::Parse::VERSION\n" | 
| 408 |  |  |  |  |  |  | } | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | ### default if IPTables::Parse version < 1.2 | 
| 411 | 0 |  |  |  |  |  | my @parse_keys = (qw(protocol s_port d_port to_ip | 
| 412 |  |  |  |  |  |  | to_port mac_source state ctstate)); | 
| 413 |  |  |  |  |  |  |  | 
| 414 | 0 | 0 |  |  |  |  | if ($IPTables::Parse::VERSION > 1.1) { | 
| 415 |  |  |  |  |  |  |  | 
| 416 | 0 |  |  |  |  |  | @parse_keys = (); | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | ### get the keys list from the IPTables::Parse module | 
| 419 | 0 |  |  |  |  |  | for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'regular'}}) { | 
|  | 0 |  |  |  |  |  |  | 
| 420 | 0 |  |  |  |  |  | push @parse_keys, $key; | 
| 421 |  |  |  |  |  |  | } | 
| 422 | 0 |  |  |  |  |  | for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'extended'}}) { | 
|  | 0 |  |  |  |  |  |  | 
| 423 | 0 |  |  |  |  |  | push @parse_keys, $key; | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | ### make sure that an unsupported search criteria is not required | 
| 427 | 0 | 0 |  |  |  |  | if (keys %$extended_hr) { | 
| 428 | 0 |  |  |  |  |  | for my $key (keys %$extended_hr) { | 
| 429 | 0 | 0 |  |  |  |  | next if $key eq 'normalize'; | 
| 430 | 0 |  |  |  |  |  | my $found = 0; | 
| 431 | 0 |  |  |  |  |  | for my $supported_key (@parse_keys) { | 
| 432 | 0 | 0 |  |  |  |  | if ($key eq $supported_key) { | 
| 433 | 0 |  |  |  |  |  | $found = 1; | 
| 434 | 0 |  |  |  |  |  | last; | 
| 435 |  |  |  |  |  |  | } | 
| 436 |  |  |  |  |  |  | } | 
| 437 | 0 | 0 |  |  |  |  | unless ($found) { | 
| 438 | 0 |  |  |  |  |  | croak "[*] Extended hash search key '$key' not " . | 
| 439 |  |  |  |  |  |  | "supported by IPTables::Parse"; | 
| 440 |  |  |  |  |  |  | } | 
| 441 |  |  |  |  |  |  | } | 
| 442 |  |  |  |  |  |  | } | 
| 443 |  |  |  |  |  |  | } | 
| 444 |  |  |  |  |  |  |  | 
| 445 | 0 |  |  |  |  |  | my $chain_ar = $self->{'parse_obj'}->chain_rules($table, $chain); | 
| 446 |  |  |  |  |  |  |  | 
| 447 | 0 | 0 | 0 |  |  |  | $src = $self->normalize_net($src) if defined $extended_hr->{'normalize'} | 
| 448 |  |  |  |  |  |  | and $extended_hr->{'normalize'}; | 
| 449 | 0 | 0 | 0 |  |  |  | $dst = $self->normalize_net($dst) if defined $extended_hr->{'normalize'} | 
| 450 |  |  |  |  |  |  | and $extended_hr->{'normalize'}; | 
| 451 |  |  |  |  |  |  |  | 
| 452 | 0 |  |  |  |  |  | my $rulenum = 1; | 
| 453 | 0 |  |  |  |  |  | for my $rule_hr (@$chain_ar) { | 
| 454 | 0 | 0 | 0 |  |  |  | if ($rule_hr->{'target'} eq $target | 
|  |  |  | 0 |  |  |  |  | 
| 455 |  |  |  |  |  |  | and $rule_hr->{'src'} eq $src | 
| 456 |  |  |  |  |  |  | and $rule_hr->{'dst'} eq $dst) { | 
| 457 | 0 | 0 |  |  |  |  | if (keys %$extended_hr) { | 
| 458 | 0 |  |  |  |  |  | my $found = 1; | 
| 459 | 0 |  |  |  |  |  | for my $key (@parse_keys) { | 
| 460 | 0 | 0 |  |  |  |  | if (defined $extended_hr->{$key}) { | 
| 461 | 0 | 0 |  |  |  |  | if (defined $rule_hr->{$key}) { | 
| 462 | 0 | 0 | 0 |  |  |  | if ($key eq 'state' or $key eq 'ctstate') { | 
|  |  | 0 |  |  |  |  |  | 
| 463 |  |  |  |  |  |  | ### make sure that state ordering as reported | 
| 464 |  |  |  |  |  |  | ### by iptables is accounted for vs. what was | 
| 465 |  |  |  |  |  |  | ### supplied to the module | 
| 466 | 0 | 0 |  |  |  |  | unless (&state_compare($extended_hr->{$key}, | 
| 467 |  |  |  |  |  |  | $rule_hr->{$key})) { | 
| 468 | 0 |  |  |  |  |  | $found = 0; | 
| 469 | 0 |  |  |  |  |  | last; | 
| 470 |  |  |  |  |  |  | } | 
| 471 |  |  |  |  |  |  | } elsif ($key eq 'mac_source') { | 
| 472 |  |  |  |  |  |  | ### make sure case does not matter | 
| 473 | 0 | 0 |  |  |  |  | unless (lc($extended_hr->{$key}) | 
| 474 |  |  |  |  |  |  | eq lc($rule_hr->{$key})) { | 
| 475 | 0 |  |  |  |  |  | $found = 0; | 
| 476 | 0 |  |  |  |  |  | last; | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  | } else { | 
| 479 | 0 | 0 |  |  |  |  | unless ($extended_hr->{$key} | 
| 480 |  |  |  |  |  |  | eq $rule_hr->{$key}) { | 
| 481 | 0 |  |  |  |  |  | $found = 0; | 
| 482 | 0 |  |  |  |  |  | last; | 
| 483 |  |  |  |  |  |  | } | 
| 484 |  |  |  |  |  |  | } | 
| 485 |  |  |  |  |  |  | } else { | 
| 486 | 0 |  |  |  |  |  | $found = 0; | 
| 487 | 0 |  |  |  |  |  | last; | 
| 488 |  |  |  |  |  |  | } | 
| 489 |  |  |  |  |  |  | } | 
| 490 |  |  |  |  |  |  | } | 
| 491 | 0 | 0 |  |  |  |  | return $rulenum, $#$chain_ar+1 if $found; | 
| 492 |  |  |  |  |  |  | } else { | 
| 493 | 0 | 0 |  |  |  |  | if ($rule_hr->{'protocol'} eq 'all') { | 
| 494 | 0 | 0 | 0 |  |  |  | if ($target eq 'LOG' or $target eq 'ULOG') { | 
|  |  | 0 |  |  |  |  |  | 
| 495 |  |  |  |  |  |  | ### built-in LOG and ULOG target rules always | 
| 496 |  |  |  |  |  |  | ### have extended information | 
| 497 | 0 |  |  |  |  |  | return $rulenum, $#$chain_ar+1; | 
| 498 |  |  |  |  |  |  | } elsif (not $rule_hr->{'extended'}) { | 
| 499 |  |  |  |  |  |  | ### don't want any additional criteria (such as | 
| 500 |  |  |  |  |  |  | ### port numbers) in the rule. Note that we are | 
| 501 |  |  |  |  |  |  | ### also not checking interfaces | 
| 502 | 0 |  |  |  |  |  | return $rulenum, $#$chain_ar+1; | 
| 503 |  |  |  |  |  |  | } | 
| 504 |  |  |  |  |  |  | } | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  | } | 
| 507 | 0 |  |  |  |  |  | $rulenum++; | 
| 508 |  |  |  |  |  |  | } | 
| 509 | 0 |  |  |  |  |  | return 0, $#$chain_ar+1; | 
| 510 |  |  |  |  |  |  | } | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | sub print_parse_capabilities() { | 
| 513 | 0 |  |  | 0 | 0 |  | my $self = shift; | 
| 514 |  |  |  |  |  |  |  | 
| 515 | 0 | 0 |  |  |  |  | if ($IPTables::Parse::VERSION > 1.1) { | 
| 516 |  |  |  |  |  |  |  | 
| 517 | 0 |  |  |  |  |  | print "[+] IPTables::Parse regular options:\n"; | 
| 518 | 0 |  |  |  |  |  | for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'regular'}}) { | 
|  | 0 |  |  |  |  |  |  | 
| 519 | 0 |  |  |  |  |  | my $p_hr = $self->{'parse_obj'}->{'parse_keys'}->{'regular'}->{$key}; | 
| 520 | 0 |  |  |  |  |  | print "    $key\n"; | 
| 521 | 0 | 0 | 0 |  |  |  | if (defined $p_hr->{'regex'} and $p_hr->{'regex'}) { | 
| 522 | 0 |  |  |  |  |  | print "      regex: $p_hr->{'regex'}", "\n"; | 
| 523 |  |  |  |  |  |  | } | 
| 524 | 0 | 0 | 0 |  |  |  | if (defined $p_hr->{'ipt_match'} and $p_hr->{'ipt_match'}) { | 
| 525 | 0 |  |  |  |  |  | print "      ipt_match: $p_hr->{'ipt_match'} ", "\n"; | 
| 526 |  |  |  |  |  |  | } | 
| 527 |  |  |  |  |  |  | } | 
| 528 |  |  |  |  |  |  |  | 
| 529 | 0 |  |  |  |  |  | print "\n[+] IPTables::Parse extended options:\n"; | 
| 530 | 0 |  |  |  |  |  | for my $key (keys %{$self->{'parse_obj'}->{'parse_keys'}->{'extended'}}) { | 
|  | 0 |  |  |  |  |  |  | 
| 531 | 0 |  |  |  |  |  | my $p_hr = $self->{'parse_obj'}->{'parse_keys'}->{'extended'}->{$key}; | 
| 532 | 0 |  |  |  |  |  | print "    $key\n"; | 
| 533 | 0 | 0 | 0 |  |  |  | if (defined $p_hr->{'regex'} and $p_hr->{'regex'}) { | 
| 534 | 0 |  |  |  |  |  | print "      regex: $p_hr->{'regex'}", "\n"; | 
| 535 |  |  |  |  |  |  | } | 
| 536 | 0 | 0 | 0 |  |  |  | if (defined $p_hr->{'ipt_match'} and $p_hr->{'ipt_match'}) { | 
| 537 | 0 |  |  |  |  |  | print "      ipt_match: $p_hr->{'ipt_match'} ", "\n"; | 
| 538 |  |  |  |  |  |  | } | 
| 539 |  |  |  |  |  |  | } | 
| 540 |  |  |  |  |  |  |  | 
| 541 |  |  |  |  |  |  | } else { | 
| 542 | 0 |  |  |  |  |  | print "[+] IPTables::Parse capabilities:\n"; | 
| 543 | 0 |  |  |  |  |  | for my $key (qw(protocol s_port d_port to_ip | 
| 544 |  |  |  |  |  |  | to_port mac_source state ctstate)) { | 
| 545 | 0 |  |  |  |  |  | print "    $key\n"; | 
| 546 |  |  |  |  |  |  | } | 
| 547 |  |  |  |  |  |  | } | 
| 548 | 0 |  |  |  |  |  | return; | 
| 549 |  |  |  |  |  |  | } | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | sub state_compare() { | 
| 552 | 0 |  |  | 0 | 0 |  | my ($state_str1, $state_str2) = @_; | 
| 553 |  |  |  |  |  |  |  | 
| 554 | 0 |  |  |  |  |  | my @states1 = split /,/, $state_str1; | 
| 555 | 0 |  |  |  |  |  | my @states2 = split /,/, $state_str2; | 
| 556 |  |  |  |  |  |  |  | 
| 557 | 0 |  |  |  |  |  | for my $state1 (@states1) { | 
| 558 | 0 |  |  |  |  |  | my $found = 0; | 
| 559 | 0 |  |  |  |  |  | for my $state2 (@states2) { | 
| 560 | 0 | 0 |  |  |  |  | if ($state1 eq $state2) { | 
| 561 | 0 |  |  |  |  |  | $found = 1; | 
| 562 | 0 |  |  |  |  |  | last; | 
| 563 |  |  |  |  |  |  | } | 
| 564 |  |  |  |  |  |  | } | 
| 565 | 0 | 0 |  |  |  |  | return 0 unless $found; | 
| 566 |  |  |  |  |  |  | } | 
| 567 |  |  |  |  |  |  |  | 
| 568 | 0 |  |  |  |  |  | for my $state2 (@states2) { | 
| 569 | 0 |  |  |  |  |  | my $found = 0; | 
| 570 | 0 |  |  |  |  |  | for my $state1 (@states1) { | 
| 571 | 0 | 0 |  |  |  |  | if ($state2 eq $state1) { | 
| 572 | 0 |  |  |  |  |  | $found = 1; | 
| 573 | 0 |  |  |  |  |  | last; | 
| 574 |  |  |  |  |  |  | } | 
| 575 |  |  |  |  |  |  | } | 
| 576 | 0 | 0 |  |  |  |  | return 0 unless $found; | 
| 577 |  |  |  |  |  |  | } | 
| 578 |  |  |  |  |  |  |  | 
| 579 | 0 |  |  |  |  |  | return 1; | 
| 580 |  |  |  |  |  |  | } | 
| 581 |  |  |  |  |  |  |  | 
| 582 |  |  |  |  |  |  | sub normalize_net() { | 
| 583 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 584 | 0 |  | 0 |  |  |  | my $net  = shift || croak '[*] Must specify net.'; | 
| 585 |  |  |  |  |  |  |  | 
| 586 | 0 |  |  |  |  |  | my $normalized_net = $net;  ### establish default | 
| 587 |  |  |  |  |  |  |  | 
| 588 |  |  |  |  |  |  | ### regex to match an IPv4 address | 
| 589 | 0 |  |  |  |  |  | my $ipv4_re = qr/(?:\d{1,3}\.){3}\d{1,3}/; | 
| 590 |  |  |  |  |  |  |  | 
| 591 | 0 | 0 | 0 |  |  |  | if ($net =~ m|/| and $net =~ $ipv4_re or $net =~ m|:|) { | 
|  |  |  | 0 |  |  |  |  | 
| 592 | 0 | 0 |  |  |  |  | if ($net =~ m|:|) {  ### an IPv6 address | 
| 593 | 0 | 0 |  |  |  |  | my $n = NetAddr::IP->new6($net) | 
| 594 |  |  |  |  |  |  | or croak "[*] Could not acquire NetAddr::IP object for $net"; | 
| 595 | 0 |  |  |  |  |  | $normalized_net = lc($n->network()->short()) . '/' . $n->masklen(); | 
| 596 | 0 |  |  |  |  |  | $normalized_net =~ s|/128$||; | 
| 597 |  |  |  |  |  |  | } else { | 
| 598 | 0 | 0 |  |  |  |  | my $n = NetAddr::IP->new($net) | 
| 599 |  |  |  |  |  |  | or croak "[*] Could not acquire NetAddr::IP object for $net"; | 
| 600 | 0 |  |  |  |  |  | $normalized_net = $n->network()->cidr(); | 
| 601 | 0 |  |  |  |  |  | $normalized_net =~ s|/32$||; | 
| 602 |  |  |  |  |  |  | } | 
| 603 |  |  |  |  |  |  | } | 
| 604 | 0 |  |  |  |  |  | return $normalized_net; | 
| 605 |  |  |  |  |  |  | } | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | sub add_jump_rule() { | 
| 608 | 0 |  |  | 0 | 1 |  | my $self  = shift; | 
| 609 | 0 |  | 0 |  |  |  | my $table = shift || croak '[-] Must specify a table, e.g. "filter".'; | 
| 610 | 0 |  | 0 |  |  |  | my $from_chain = shift || croak '[-] Must specify chain to jump from.'; | 
| 611 | 0 |  | 0 |  |  |  | my $rulenum    = shift || croak '[-] Must specify jump rule chain position'; | 
| 612 | 0 |  | 0 |  |  |  | my $to_chain   = shift || croak '[-] Must specify chain to jump to.'; | 
| 613 | 0 |  |  |  |  |  | my $idx_err = ''; | 
| 614 |  |  |  |  |  |  |  | 
| 615 | 0 | 0 |  |  |  |  | if ($from_chain eq $to_chain) { | 
| 616 | 0 |  |  |  |  |  | return 0, ["Identical from_chain and to_chain ($from_chain) " . | 
| 617 |  |  |  |  |  |  | "not allowed."], []; | 
| 618 |  |  |  |  |  |  | } | 
| 619 |  |  |  |  |  |  |  | 
| 620 | 0 |  |  |  |  |  | my $ip_any_net = '0.0.0.0/0'; | 
| 621 | 0 | 0 |  |  |  |  | $ip_any_net = '::/0' if $self->{'_ipv6'}; | 
| 622 |  |  |  |  |  |  |  | 
| 623 |  |  |  |  |  |  | ### first check to see if the jump rule already exists | 
| 624 | 0 |  |  |  |  |  | my ($rule_position, $num_chain_rules) | 
| 625 |  |  |  |  |  |  | = $self->find_ip_rule($ip_any_net, $ip_any_net, $table, | 
| 626 |  |  |  |  |  |  | $from_chain, $to_chain, {}); | 
| 627 |  |  |  |  |  |  |  | 
| 628 |  |  |  |  |  |  | ### check to see if the insertion index ($rulenum) is too big | 
| 629 | 0 | 0 |  |  |  |  | $rulenum = 1 if $rulenum <= 0; | 
| 630 | 0 | 0 |  |  |  |  | if ($rulenum > $num_chain_rules+1) { | 
| 631 | 0 | 0 |  |  |  |  | $idx_err = "Rule position $rulenum is past end of $from_chain " . | 
| 632 |  |  |  |  |  |  | "chain ($num_chain_rules rules), compensating." | 
| 633 |  |  |  |  |  |  | if $num_chain_rules > 0; | 
| 634 | 0 |  |  |  |  |  | $rulenum = $num_chain_rules + 1; | 
| 635 |  |  |  |  |  |  | } | 
| 636 | 0 | 0 |  |  |  |  | $rulenum = 1 if $rulenum == 0; | 
| 637 |  |  |  |  |  |  |  | 
| 638 | 0 | 0 |  |  |  |  | if ($rule_position) { | 
| 639 |  |  |  |  |  |  | ### the rule already exists | 
| 640 | 0 |  |  |  |  |  | return 1, | 
| 641 |  |  |  |  |  |  | ["Table: $table, chain: $to_chain, jump rule already exists."], []; | 
| 642 |  |  |  |  |  |  | } | 
| 643 |  |  |  |  |  |  |  | 
| 644 |  |  |  |  |  |  | ### we need to add the rule | 
| 645 | 0 |  |  |  |  |  | my ($rv, $out_ar, $err_ar) = $self->run_ipt_cmd( | 
| 646 |  |  |  |  |  |  | "$self->{'_cmd'} -t $table -I $from_chain $rulenum -j $to_chain"); | 
| 647 | 0 | 0 |  |  |  |  | push @$err_ar, $idx_err if $idx_err; | 
| 648 | 0 |  |  |  |  |  | return $rv, $out_ar, $err_ar; | 
| 649 |  |  |  |  |  |  | } | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | sub REAPER { | 
| 652 | 0 |  |  | 0 | 0 |  | my $stiff; | 
| 653 | 0 |  |  |  |  |  | while(($stiff = waitpid(-1,WNOHANG))>0){ | 
| 654 |  |  |  |  |  |  | # do something with $stiff if you want | 
| 655 |  |  |  |  |  |  | } | 
| 656 | 0 |  |  |  |  |  | local $SIG{'CHLD'} = \&REAPER; | 
| 657 | 0 |  |  |  |  |  | return; | 
| 658 |  |  |  |  |  |  | } | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | sub run_ipt_cmd() { | 
| 661 | 0 |  |  | 0 | 1 |  | my $self  = shift; | 
| 662 | 0 |  | 0 |  |  |  | my $cmd = shift || croak qq|[*] Must specify | . | 
| 663 |  |  |  |  |  |  | qq|$self->{'_ipt_bin_name'} command to run.|; | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | ### iptables execution is provided by IPTables::Parse which is | 
| 666 |  |  |  |  |  |  | ### a dependency of IPTables::ChainMgr | 
| 667 | 0 |  |  |  |  |  | return $self->{'parse_obj'}->exec_iptables($cmd); | 
| 668 |  |  |  |  |  |  | } | 
| 669 |  |  |  |  |  |  |  | 
| 670 |  |  |  |  |  |  | 1; | 
| 671 |  |  |  |  |  |  |  | 
| 672 |  |  |  |  |  |  | __END__ |