App-HTTP_Proxy_IMP
view release on metacpan or search on metacpan
lib/App/HTTP_Proxy_IMP.pm view on Meta::CPAN
use strict;
use warnings;
package App::HTTP_Proxy_IMP;
our $VERSION = '0.958';
use fields (
'addr', # \@addr to listen on
'impns', # \@namespace for IMP plugins
'filter', # \@plugins to load
'logrx', # regexp for filtering log messages
'pcapdir', # dir to store pcap files of requests
'mitm_ca', # file containing cert and key of proxy cert
'capath', # path to CA to verify server cert
'no_check_certificate', # don't check server certificates
'childs', # use this number of childs ( 0 = don't fork)
'max_connect_per_child', # max number of connections before child exits
);
use App::HTTP_Proxy_IMP::IMP;
use App::HTTP_Proxy_IMP::Conn;
use App::HTTP_Proxy_IMP::Request;
use App::HTTP_Proxy_IMP::Relay;
use AnyEvent;
use Getopt::Long qw(:config posix_default bundling);
use App::HTTP_Proxy_IMP::Debug qw(debug $DEBUG $DEBUG_RX);
use Net::Inspect::Debug qw(%TRACE);
use IO::Socket::SSL::Intercept;
use IO::Socket::SSL::Utils;
use Carp 'croak';
use POSIX '_exit';
# try IPv6 using IO::Socket::IP or IO::Socket::INET6
# fallback to IPv4 only
my $sockclass;
BEGIN {
for(qw( IO::Socket::IP IO::Socket::INET6 IO::Socket::INET )) {
if ( eval "require $_" ) {
$sockclass = $_;
last;
}
}
$sockclass or die "cannot find usable socket class";
}
sub new {
my ($class,@args) = @_;
my $self = fields::new($class);
$self->{impns} = [qw(App::HTTP_Proxy_IMP::IMP Net::IMP::HTTP Net::IMP)];
%$self = ( %$self, %{ shift(@args) }) if @args && ref($args[0]);
$self->getoptions(@args) if @args;
return $self;
}
sub start {
my $self = shift;
$self = $self->new(@_) or return if ! ref($self); # package->start
my $pcapdir = $self->{pcapdir};
if ( $pcapdir ) {
croak("pcap directory not writeable") unless -d $pcapdir && -w _;
eval { require Net::PcapWriter } or croak(
"cannot load Net::PcapWriter, which is needed with --pcapdir option");
}
my $mitm;
if ( my $f = $self->{mitm_ca} ) {
my $serial = 1;
my $cache = {};
my $cachedir = "$f.cache";
if ( -d $cachedir || mkdir($cachedir,0700)) {
for my $f (glob("$cachedir/*.pem")) {
-f $f && -r _ && -s _ or next;
my $time = (stat(_))[9];
my $key = PEM_file2key($f) or next;
my $cert = PEM_file2cert($f) or next;
my $sn = CERT_asHash($cert)->{serial};
$serial = $sn+1 if $sn>=$serial;
my ($id) = $f=~m{/([^/]+)\.pem$};
$cache->{$id} = {
cert => $cert,
key => $key,
atime => $time,
};
debug("loaded certificate id=$id from cache");
}
my $cache_hash = $cache;
$cache = sub {
my $id = shift;
my $e;
if ( ! @_ ){ # get
$e = $cache_hash->{$id} or return;
} else {
my ($cert,$key) = @_;
$e = $cache_hash->{$id} = {
cert => $cert,
key => $key,
};
}
my $f = "$cachedir/$id.pem";
if ( @_ || ! -f $f and open( my $fh,">",$f )) {
debug("save mitm certificate and key to $cachedir/$id.pem");
print $fh PEM_cert2string($e->{cert}),
PEM_key2string($e->{key})
} else {
utime(undef,undef,$f);
}
$e->{atime} = time();
return ($e->{cert},$e->{key});
};
}
$mitm = IO::Socket::SSL::Intercept->new(
proxy_cert_file => $f,
proxy_key_file => $f,
cache => $cache,
serial => $serial,
);
}
my $imp_factory;
my $filter = $self->{filter};
if ($filter && @$filter ) {
my $ns = $self->{impns};
my @mod;
my $ev = App::HTTP_Proxy_IMP::EventLoop->new;
for my $f (@$filter) {
if ( ref($f) ) {
# already factory object
push @mod,$f;
next;
}
my $f = $f; # copy
my $args = $f =~s{=(.*)}{} && $1;
my $found;
for my $prefix ('', map { "${_}::" } @$ns) {
my $mod = $prefix.$f;
if ( eval "require $mod" ) {
$found = $mod;
last;
}
}
croak("IMP module $f could not be loaded: $@") if ! $found;
my %args = $args ? $found->str2cfg($args) :();
my @err = $found->validate_cfg(%args);
die "bad config for $found: @err" if @err;
push @mod, $found->new_factory(%args, eventlib => $ev )
}
my $logsub = $self->{logrx} && do {
my $rx = $self->{logrx};
sub {
my ($level,$msg,$dir,$off,$len) = @_;
$level =~ $rx or return;
print STDERR "[$level]($dir:$off,$len) $msg\n";
};
};
$imp_factory = App::HTTP_Proxy_IMP::IMP->new_factory(
mod => \@mod,
logsub => $logsub,
);
}
if ( $self->{childs} ) {
$self->{childs} = [ map { undef } (1..$self->{childs}) ];
}
my $capath;
if ( ! $mitm ) {
# no interception = no certificate checking
} elsif ( $self->{no_check_certificate} ) {
( run in 1.218 second using v1.01-cache-2.11-cpan-39bf76dae61 )