Reverting more fucked up untested code from r21448 and r21449
[lhc/web/wiklou.git] / includes / ProxyTools.php
1 <?php
2 /**
3 * Functions for dealing with proxies
4 */
5
6 /**
7 * Extracts the XFF string from the request header
8 * Checks first for "X-Forwarded-For", then "Client-ip"
9 * Note: headers are spoofable
10 * @return string
11 */
12 function wfGetForwardedFor() {
13 if( function_exists( 'apache_request_headers' ) ) {
14 // More reliable than $_SERVER due to case and -/_ folding
15 $set = apache_request_headers();
16 $index = 'X-Forwarded-For';
17 $index2 = 'Client-ip';
18 } else {
19 // Subject to spoofing with headers like X_Forwarded_For
20 $set = $_SERVER;
21 $index = 'HTTP_X_FORWARDED_FOR';
22 $index2 = 'CLIENT-IP';
23 }
24 #Try a couple of headers
25 if( isset( $set[$index] ) ) {
26 return $set[$index];
27 } else if( isset( $set[$index2] ) ) {
28 return $set[$index2];
29 } else {
30 return null;
31 }
32 }
33
34 /**
35 * @todo FUCKING DOCUMENT THIS FUCKING FUNCTION
36 */
37 function wfGetLastIPfromXFF( $xff ) {
38 if ( $xff ) {
39 // Avoid annoyingly long xff hacks
40 $xff = substr( $xff, 0, 255 );
41 // Look for the last IP, assuming they are separated by commas or spaces
42 $n = ( strrpos($xff, ',') ) ? strrpos($xff, ',') : strrpos($xff, ' ');
43 if ( $n !== false ) {
44 $last = trim( substr( $xff, $n + 1 ) );
45 // Make sure it is an IP
46 $m = preg_match('#^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $last);
47 $n = preg_match('#^:(:[0-9A-Fa-f]{1,4}){1,7}|[0-9A-Fa-f]{1,4}(:{1,2}[0-9A-Fa-f]{1,4}|::$){1,7}$#', $last);
48 if ( $m > 0 )
49 $xff_ip = $last;
50 else if ( $n > 0 )
51 $xff_ip = $last;
52 else
53 $xff_ip = null;
54 } else {
55 $xff_ip = null;
56 }
57 } else {
58 $xff_ip = null;
59 }
60 return $xff_ip;
61 }
62
63 /**
64 * Returns the browser/OS data from the request header
65 * Note: headers are spoofable
66 * @return string
67 */
68 function wfGetAgent() {
69 if( function_exists( 'apache_request_headers' ) ) {
70 // More reliable than $_SERVER due to case and -/_ folding
71 $set = apache_request_headers();
72 $index = 'User-Agent';
73 } else {
74 // Subject to spoofing with headers like X_Forwarded_For
75 $set = $_SERVER;
76 $index = 'HTTP_USER_AGENT';
77 }
78 if( isset( $set[$index] ) ) {
79 return $set[$index];
80 } else {
81 return '';
82 }
83 }
84
85 /**
86 * Work out the IP address based on various globals
87 * For trusted proxies, use the XFF client IP (first of the chain)
88 * @return string
89 */
90 function wfGetIP() {
91 global $wgIP;
92
93 # Return cached result
94 if ( !empty( $wgIP ) ) {
95 return $wgIP;
96 }
97
98 /* collect the originating ips */
99 # Client connecting to this webserver
100 if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
101 $ipchain = array( IP::canonicalize( $_SERVER['REMOTE_ADDR'] ) );
102 } else {
103 # Running on CLI?
104 $ipchain = array( '127.0.0.1' );
105 }
106 $ip = $ipchain[0];
107
108 # Append XFF on to $ipchain
109 $forwardedFor = wfGetForwardedFor();
110 if ( isset( $forwardedFor ) ) {
111 $xff = array_map( 'trim', explode( ',', $forwardedFor ) );
112 $xff = array_reverse( $xff );
113 $ipchain = array_merge( $ipchain, $xff );
114 }
115
116 # Step through XFF list and find the last address in the list which is a trusted server
117 # Set $ip to the IP address given by that trusted server, unless the address is not sensible (e.g. private)
118 foreach ( $ipchain as $i => $curIP ) {
119 $curIP = IP::canonicalize( $curIP );
120 if ( wfIsTrustedProxy( $curIP ) ) {
121 if ( isset( $ipchain[$i + 1] ) && IP::isPublic( $ipchain[$i + 1] ) ) {
122 $ip = $ipchain[$i + 1];
123 }
124 } else {
125 break;
126 }
127 }
128
129 wfDebug( "IP: $ip\n" );
130 $wgIP = $ip;
131 return $ip;
132 }
133
134 /**
135 * Checks if an IP is a trusted proxy providor
136 * Useful to tell if X-Fowarded-For data is possibly bogus
137 * Squid cache servers for the site and AOL are whitelisted
138 * @param string $ip
139 * @return bool
140 */
141 function wfIsTrustedProxy( $ip ) {
142 global $wgSquidServers, $wgSquidServersNoPurge;
143
144 if ( in_array( $ip, $wgSquidServers ) ||
145 in_array( $ip, $wgSquidServersNoPurge ) ||
146 wfIsAOLProxy( $ip )
147 ) {
148 $trusted = true;
149 } else {
150 $trusted = false;
151 }
152 wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) );
153 return $trusted;
154 }
155
156 /**
157 * Forks processes to scan the originating IP for an open proxy server
158 * MemCached can be used to skip IPs that have already been scanned
159 */
160 function wfProxyCheck() {
161 global $wgBlockOpenProxies, $wgProxyPorts, $wgProxyScriptPath;
162 global $wgUseMemCached, $wgMemc, $wgProxyMemcExpiry;
163 global $wgProxyKey;
164
165 if ( !$wgBlockOpenProxies ) {
166 return;
167 }
168
169 $ip = wfGetIP();
170
171 # Get MemCached key
172 $skip = false;
173 if ( $wgUseMemCached ) {
174 $mcKey = wfMemcKey( 'proxy', 'ip', $ip );
175 $mcValue = $wgMemc->get( $mcKey );
176 if ( $mcValue ) {
177 $skip = true;
178 }
179 }
180
181 # Fork the processes
182 if ( !$skip ) {
183 $title = SpecialPage::getTitleFor( 'Blockme' );
184 $iphash = md5( $ip . $wgProxyKey );
185 $url = $title->getFullURL( 'ip='.$iphash );
186
187 foreach ( $wgProxyPorts as $port ) {
188 $params = implode( ' ', array(
189 escapeshellarg( $wgProxyScriptPath ),
190 escapeshellarg( $ip ),
191 escapeshellarg( $port ),
192 escapeshellarg( $url )
193 ));
194 exec( "php $params &>/dev/null &" );
195 }
196 # Set MemCached key
197 if ( $wgUseMemCached ) {
198 $wgMemc->set( $mcKey, 1, $wgProxyMemcExpiry );
199 }
200 }
201 }
202
203 /**
204 * Convert a network specification in CIDR notation to an integer network and a number of bits
205 * @return array(string, int)
206 */
207 function wfParseCIDR( $range ) {
208 return IP::parseCIDR( $range );
209 }
210
211 /**
212 * Check if an IP address is in the local proxy list
213 * @return bool
214 */
215 function wfIsLocallyBlockedProxy( $ip ) {
216 global $wgProxyList;
217 $fname = 'wfIsLocallyBlockedProxy';
218
219 if ( !$wgProxyList ) {
220 return false;
221 }
222 wfProfileIn( $fname );
223
224 if ( !is_array( $wgProxyList ) ) {
225 # Load from the specified file
226 $wgProxyList = array_map( 'trim', file( $wgProxyList ) );
227 }
228
229 if ( !is_array( $wgProxyList ) ) {
230 $ret = false;
231 } elseif ( array_search( $ip, $wgProxyList ) !== false ) {
232 $ret = true;
233 } elseif ( array_key_exists( $ip, $wgProxyList ) ) {
234 # Old-style flipped proxy list
235 $ret = true;
236 } else {
237 $ret = false;
238 }
239 wfProfileOut( $fname );
240 return $ret;
241 }
242
243 /**
244 * TODO: move this list to the database in a global IP info table incorporating
245 * trusted ISP proxies, blocked IP addresses and open proxies.
246 * @return bool
247 */
248 function wfIsAOLProxy( $ip ) {
249 $ranges = array(
250 '64.12.96.0/19',
251 '149.174.160.0/20',
252 '152.163.240.0/21',
253 '152.163.248.0/22',
254 '152.163.252.0/23',
255 '152.163.96.0/22',
256 '152.163.100.0/23',
257 '195.93.32.0/22',
258 '195.93.48.0/22',
259 '195.93.64.0/19',
260 '195.93.96.0/19',
261 '195.93.16.0/20',
262 '198.81.0.0/22',
263 '198.81.16.0/20',
264 '198.81.8.0/23',
265 '202.67.64.128/25',
266 '205.188.192.0/20',
267 '205.188.208.0/23',
268 '205.188.112.0/20',
269 '205.188.146.144/30',
270 '207.200.112.0/21',
271 );
272
273 static $parsedRanges;
274 if ( is_null( $parsedRanges ) ) {
275 $parsedRanges = array();
276 foreach ( $ranges as $range ) {
277 $parsedRanges[] = IP::parseRange( $range );
278 }
279 }
280
281 $hex = IP::toHex( $ip );
282 foreach ( $parsedRanges as $range ) {
283 if ( $hex >= $range[0] && $hex <= $range[1] ) {
284 return true;
285 }
286 }
287 return false;
288 }
289
290
291
292 ?>