Net-IMP
view release on metacpan or search on metacpan
lib/Net/IMP/ProtocolPinning.pm view on Meta::CPAN
$DEBUG && debug("completed preliminary match rule $crs->[0]");
$self->{off_buf}[$dir] += $removed;
if ( $removed > $match_in_progress ) {
$pass_until = $self->{off_passed}[$dir]
= $self->{off_buf}[$dir];
}
# no return, might match more
} elsif ( $matched > $match_in_progress ) {
# keep rule open but issue extended IMP_PASS
$DEBUG && debug("extended preliminary match rule $crs->[0]");
$pass_until = $self->{off_passed}[$dir]
= $self->{off_buf}[$dir]+$matched;
goto PASS_AND_RETURN; # need more data
} else {
# keep rule open waiting for more data
$DEBUG && debug("still preliminary(?) match rule $crs->[0]");
goto PASS_AND_RETURN; # need more data
}
} else {
# stream followed by packet, so rule cannot be extended
# remove from buf until end of last match
$DEBUG && debug("finished match rule $crs->[0] on packet $type");
substr($self->{buf}[$dir],0,$match_in_progress,'');
$self->{off_buf}[$dir] = $self->{off_passed}[$dir];
}
# match of previously matching rule done
# remove it and continue with next rule if there are more data
shift(@$crs);
if (! @$crs) {
shift(@$rs);
# switch to other dir if this dir is done for now
if ( ! @$rs || ! $rs->[0] ) {
my $ors = $self->{ruleset}[$dir ? 0:1];
shift @$ors if @$ors && ! $ors->[0];
goto CHECK_DONE if ! @$ors && ! @$rs;
}
}
if ( $type>0 or $self->{buf}[$dir] ne '' ) {
# unmatched data exist in data/buf
if ( ! @$rs ) {
# all rules done from this direction, put back all
# from buf to $data before calling NEXT_RULE
$data = $self->{buf}[$dir];
$self->{buf}[$dir] = '';
}
goto NEXT_RULE;
}
goto PASS_AND_RETURN; # wait for more data
}
# check against current set
if ( $type>0 ) {
# packet data
if ( $self->{buf}[$dir] ne '' ) {
$self->run_callback([
IMP_DENY,
$dir,
"packet data after unmatched streaming data"
]);
}
for( my $i=0;$i<@$crs;$i++ ) {
if ( my ($len) = $match->($rules->[$crs->[$i]],\$data)) {
# match
$pass_until = $self->{off_passed}[$dir] =
$self->{off_buf}[$dir] += $len;
if ( $self->{matched} ) {
# preserve hash of matched packet so that duplicates are
# detected later
$self->{matched}[$dir]{ md5(
( $self->{matched_seed} //= pack("N",rand(2**32)) ).
$data
)} = $crs->[$i]
}
if (@$crs>1) {
# remove rule, keep rest in ruleset
$DEBUG && debug(
"full match rule $crs->[$i] - remove from ruleset");
splice(@$crs,$i,1);
} else {
# remove ruleset with last rule in it
$DEBUG && debug(
"full match rule $crs->[$i] - remove ruleset");
shift(@$rs);
# switch to other dir if this dir is done for now
if ( ! @$rs || ! $rs->[0] ) {
my $ors = $self->{ruleset}[$dir ? 0:1];
shift @$ors if @$ors && ! $ors->[0];
}
}
# pass data
goto CHECK_DONE if ! @$rs;
goto PASS_AND_RETURN; # wait for more data
}
}
# no rule from ruleset matched, check for duplicates
if ( $self->{matched} and my $dup = $self->{matched}[$dir] ) {
my $r = $dup->{ md5($self->{matched_seed} . $data ) };
if ( defined $r ) {
# matched again - pass data
$pass_until = $self->{off_passed}[$dir]
= $self->{off_buf}[$dir] += length($data);
$DEBUG && debug("ignore DUP[$dir] for rule $r");
goto PASS_AND_RETURN; # wait for more data
}
}
# no rule and no duplicates matched, must be bad data
$DEBUG && debug("no matching rule for ${type}[$dir] - deny");
$self->{buf} = undef;
$self->run_callback([
IMP_DENY,
$dir,
"rule#@$crs did not match"
]);
return;
} else {
# streaming data
my $temp_fail;
my $final_match;
for( my $i=0;$i<@$crs;$i++ ) {
my ($len,$removed)
= $match->($rules->[$crs->[$i]],\$self->{buf}[$dir]);
if ( ! defined $len ) {
# will never match against rule
next;
} elsif ( ! $len ) {
# note that it might match if buf gets longer but check other
# rules in ruleset if they match better
$temp_fail = 1;
next;
}
if ( ! $removed and @$crs == 1 and @$rs == 1 ) {
# last rule for dir - no need to extend preliminary matches
# as long as max_unbound is not restrictive
my $ma = $self->{factory_args}{max_unbound};
if ( ! defined( $ma && $ma->[$dir] )) {
$removed = $len;
substr($self->{buf}[$dir],0,$removed,'');
}
}
# rule matched
if ( ! $removed ) {
# match might not be final, wait for more data but put rule
# at the beginning of ruleset if it's not already there
unshift @$crs,splice(@$crs,$i,1) if $i>0;
# advance off_passed, but keep off_buf
$pass_until = $self->{off_passed}[$dir]
= $self->{off_buf}[$dir] + $len;
# if this is was the last completely open rule we don't need
# to check if the matched could be extended
if (@$crs == 1 and @$rs == 1 ) {
# last rule on this side
my $ors = $self->{ruleset}[$dir?0:1];
if (
# other side has no rules
! @$ors
# other side has empty rule
or @$ors == 1 and ! $ors->[0]
# other side has single rule which matched already
or @$ors == 1 and @{ $ors->[0] } == 1 and
$self->{off_passed}[$dir?0:1]
- $self->{off_buf}[$dir?0:1] > 0 ) {
# we are done and there is no need to extend the match
@$ors = @$rs = ();
goto CHECK_DONE;
}
}
} else {
# final match of rule
$pass_until = $self->{off_passed}[$dir]
= $self->{off_buf}[$dir] += $len;
if (@$crs>1) {
# remove rule, keep rest in ruleset
$DEBUG && debug(
"full match rule $crs->[$i] - remove from ruleset");
splice(@$crs,$i,1);
} else {
# remove ruleset with last rule in it
$DEBUG && debug(
"full match rule $crs->[$i] - remove ruleset");
shift(@$rs);
# switch to other dir if this dir is done for now
if ( ! @$rs || ! $rs->[0] ) {
my $ors = $self->{ruleset}[$dir ? 0:1];
shift @$ors if @$ors && ! $ors->[0];
goto CHECK_DONE if ! @$ors && ! @$rs;
}
}
$final_match = 1;
# no allow_dup for streaming
}
# pass data
if ( $final_match and $self->{buf}[$dir] ne '' ) {
# try to match more
$data = $self->{buf}[$dir];
$self->{buf}[$dir] = '';
goto NEXT_RULE;
}
goto CHECK_DONE if ! @$rs;
goto PASS_AND_RETURN;
}
if ( ! $temp_fail ) {
# no rule and no duplicates matched, must be bad data
$DEBUG && debug("no matching rule for ${type}[$dir] - deny");
$self->{buf} = undef;
$self->run_callback([
IMP_DENY,
$dir,
"rule#@$crs did not match"
]);
}
goto PASS_AND_RETURN;
}
CHECK_DONE:
return if @$rs; # still unmatched rules
# pass only current data
goto PASS_AND_RETURN if @{$self->{ruleset}[ $dir ? 0:1 ] };
# rulesets for both dirs are done, pass all data
$DEBUG && debug("all rules done - pass rest");
$self->{buf} = undef;
my @rv = (
[ IMP_PASS,0,IMP_MAXOFFSET ],
[ IMP_PASS,1,IMP_MAXOFFSET ]
);
for(0,1) {
$self->{paused}[$_] or next;
$self->{paused}[$_] = 0;
unshift @rv, [ IMP_CONTINUE,$_ ];
}
$self->run_callback(@rv);
return;
PASS_AND_RETURN:
return if ! $pass_until;
$self->run_callback([ IMP_PASS, $dir, $pass_until ]);
return;
}
# cfg2str and str2cfg are redefined because our config hash is deeper
# nested due to rules and max_unbound
sub cfg2str {
my Net::IMP::ProtocolPinning $self = shift;
my %cfg = @_;
my $rules = delete $cfg{rules} or croak("no rules defined");
( run in 0.699 second using v1.01-cache-2.11-cpan-39bf76dae61 )