Bug 24538: Handle Net::Netmask parser errors
[koha.git] / Koha / Middleware / RealIP.pm
1 package Koha::Middleware::RealIP;
2
3 # Copyright 2019 ByWater Solutions and the Koha Dev Team
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # Koha is distributed in the hope that it will be useful, but
13 # WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20 use Modern::Perl;
21
22 use parent qw(Plack::Middleware);
23
24 use C4::Context;
25
26 use Net::Netmask;
27 use Plack::Util::Accessor qw( trusted_proxy );
28
29 =head1 METHODS
30
31 =head2 prepare_app
32
33 This method generates and stores the list of trusted ip's as Netmask objects
34 at the time Plack starts up, obviating the need to regerenate them on each request.
35
36 =cut
37
38 sub prepare_app {
39     my $self = shift;
40     $self->trusted_proxy( get_trusted_proxies() );
41 }
42
43 =head2 call
44
45 This method is called for each request, and will ensure the correct remote address
46 is set in the REMOTE_ADDR environment variable.
47
48 =cut
49
50 sub call {
51     my $self = shift;
52     my $env  = shift;
53
54     if ( $env->{HTTP_X_FORWARDED_FOR} ) {
55         my @trusted_proxy = $self->trusted_proxy ? @{ $self->trusted_proxy } : undef;
56
57         if (@trusted_proxy) {
58             my $addr = get_real_ip( $env->{REMOTE_ADDR}, $env->{HTTP_X_FORWARDED_FOR}, \@trusted_proxy );
59             $ENV{REMOTE_ADDR} = $addr;
60             $env->{REMOTE_ADDR} = $addr;
61         }
62     }
63
64     return $self->app->($env);
65 }
66
67 =head2 get_real_ip
68
69 my $address = get_real_ip( $remote_addres, $x_forwarded_for_header );
70
71 This method takes the current remote address and the x-forwarded-for header string,
72 determines the correct external ip address, and returns it.
73
74 =cut
75
76 sub get_real_ip {
77     my ( $remote_addr, $header ) = @_;
78
79     my @forwarded_for = $header =~ /([^,\s]+)/g;
80     return $remote_addr unless @forwarded_for;
81
82     my $trusted_proxies = get_trusted_proxies();
83
84     my @unconfirmed = ( @forwarded_for, $remote_addr );
85
86     my $real_ip;
87     while (my $addr = pop @unconfirmed) {
88         my $has_matched = 0;
89         foreach my $netmask (@$trusted_proxies) {
90             $has_matched++, last if $netmask->match($addr);
91         }
92         $real_ip = $addr, last unless $has_matched;
93     }
94
95     return $real_ip;
96 }
97
98 =head2 get_trusted_proxies
99
100 This method returns an arrayref of Net::Netmask objects for all
101 the trusted proxies given to Koha.
102
103 =cut
104
105 sub get_trusted_proxies {
106     my $proxies_conf = C4::Context->config('koha_trusted_proxies');
107     return unless $proxies_conf;
108     my @trusted_proxies_ip = split( / /, $proxies_conf );
109     my @trusted_proxies = ();
110     foreach my $ip (@trusted_proxies_ip){
111         my $mask = Net::Netmask->new2($ip);
112         if ($mask){
113             push(@trusted_proxies,$mask);
114         }
115         else {
116             warn "$Net::Netmask::error";
117         }
118     }
119     return \@trusted_proxies;
120 }
121
122
123 =head1 AUTHORS
124
125 Kyle M Hall <kyle@bywatersolutions.com>
126
127 =cut
128
129 1;