Apache2-xForwardedFor

 view release on metacpan or  search on metacpan

lib/Apache2/xForwardedFor.pm  view on Meta::CPAN


use Apache2::Const qw(:common);

sub handler {
	my 	( $r )= shift;
	return DECLINED if $r->uri eq '/favicon.ico';
	DEBUG && print STDERR "\n============================ Apache2::xForwardedFor";

	my 	$x_forwarded_for__header_name= $r->dir_config->get('xForwardedForAlternateHeaderName') || 'X-Forwarded-For' ;
	my 	$require_header= $r->dir_config->get('xForwardedForRequire') || undef;
	
	# for testing purposes, toss in a local header value
	TEST && $r->headers_in->set( $x_forwarded_for__header_name=> '10.0.1.140' );
	my 	$x_forwarded_for__header_value= $r->headers_in->{ $x_forwarded_for__header_name };

	# if we are requiring a header to be sent, and its not there, fail immediately
	if ( $require_header ) {
		DEBUG && print STDERR "\nRequire: true";
		if ( !$x_forwarded_for__header_value ) {
			DEBUG && print STDERR "\n \theader missing";
			return FORBIDDEN;
		}
	}

	# if we are requiring an additional header to be sent, and its not there or doesn't match, fail immediately
	if 	( my $require_header_other_name= $r->dir_config->get('xForwardedForRequireHeaderName') ) {
		if ( TEST ) {
			my 	@allowable_names= $r->dir_config->get('xForwardedForRequireHeaderValue');
			$r->headers_in->set( $require_header_other_name=> $allowable_names[0] );
		}
		DEBUG && print STDERR "\nRequire Additional Header: true";
		my 	$require_header_other_value= $r->headers_in->{ $require_header_other_name };
		if 	( !$require_header_other_value ) {
			DEBUG && print STDERR "\n \tadditional required header missing";
			return FORBIDDEN;		
		}
		my 	%values_accept= map { $_=> 1 } $r->dir_config->get('xForwardedForRequireHeaderValue');
		if ( !$values_accept{ $require_header_other_value } ) {
			DEBUG && print STDERR "\n \tadditional required header invalid";
			return FORBIDDEN;		
		}
	};
	

    # Block based on Remove / Add AcceptForwarder values
    
	my 	$_accept= 0;
    my 	$remote_ip= $r->connection->remote_ip ;
	TEST && ( $remote_ip= '192.168.1.2');
	DEBUG && print STDERR "\n remote_ip__proxy: ". $remote_ip;
		my %ips_accept= map { $_=> 1 } $r->dir_config->get('xForwardedForAccept');
		if ( exists $ips_accept{$remote_ip} ) {
			$_accept= 1;
		}
		my %ips_deny= map { $_=> 1 } $r->dir_config->get('xForwardedForDeny');
		if ( exists $ips_deny{$remote_ip} ) {
			$_accept= -1;
		}

	if 	( $_accept < 0 ) {
		DEBUG && print STDERR "\n ip in blocked list";
		return FORBIDDEN;
	}
	elsif ( !$_accept && $require_header) {
		DEBUG && print STDERR "\n ip not passed, and header required";
		return FORBIDDEN;
	}
	elsif ( !$_accept && !$require_header) {
		DEBUG && print STDERR "\n ip not passed, but header not required";
	}
	

    DEBUG && print STDERR "\n x_forwarded_for__header_value: ".$x_forwarded_for__header_value;
    # Extract the desired IP address
    if ( my ($ip)= $x_forwarded_for__header_value=~ /^([\d\.]+)/ ) {
        DEBUG && print STDERR "\n original remote_ip: ". $remote_ip;
        $r->connection->remote_ip($ip);
        DEBUG && print STDERR "\n new remote_ip: ".$r->connection->remote_ip;
    } 
    else {
        # do nothing if no ip is in forwarded-for header
		# should we toss an error if this is because we couldn't parse an ip, but the header was there?
        DEBUG && print STDERR "\n no ip change";
    }

	# stacked handlers should still run off this
	return OK;
};

=head1 NAME

Apache2::xForwardedFor - Re-set remote_ip to incoming client's ip when running mod_perl behind a reverse proxy server. 
In other words, copy the first IP from B<X-Forwarded-For> header, which was set by your reverse proxy server, 
to the B<remote_ip> connection property.

=head1 SYNOPSIS

  in httpd.conf

    PerlModule Apache2::xForwardedFor
	PerlSetVar  xForwardedForRequire 1
	PerlSetVar  xForwardedForAccept 192.168.1.1
	PerlAddVar  xForwardedForAccept 192.168.1.2
	PerlPostReadRequestHandler Apache2::xForwardedFor
	
  also note:
  	PerlSetVar  xForwardedForRequireHeaderName X-Internal-Password
	PerlSetVar  xForwardedForRequireHeaderValue shibby

  or:
    PerlSetVar  xForwardedForRequireHeaderName X-Forwarded-Server
	PerlSetVar  xForwardedForRequireHeaderValue lanServer1
	PerlAddVar  xForwardedForRequireHeaderValue lanServer2


=head1 USAGE

At this time you simply need to load the module and add it to the PerlPostReadRequestHandler phase of your mod_perl-enabled httpd, and set a few variables.

Apache2::xForwardedFor is really flexible and does some very odd ( but neat! ) things 



( run in 1.099 second using v1.01-cache-2.11-cpan-bbe5e583499 )