Completed
Push — fix/remove-local-ip-check ( 981e90...86f775 )
by
unknown
17:32
created

class.jetpack-data.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
class Jetpack_Data {
4
	/**
5
	 * Gets locally stored token
6
	 *
7
	 * @return object|false
8
	 */
9
	public static function get_access_token( $user_id = false ) {
10
		if ( $user_id ) {
11
			if ( !$tokens = Jetpack_Options::get_option( 'user_tokens' ) ) {
12
				return false;
13
			}
14
			if ( $user_id === JETPACK_MASTER_USER ) {
15
				if ( !$user_id = Jetpack_Options::get_option( 'master_user' ) ) {
16
					return false;
17
				}
18
			}
19
			if ( !isset( $tokens[$user_id] ) || !$token = $tokens[$user_id] ) {
20
				return false;
21
			}
22
			$token_chunks = explode( '.', $token );
23
			if ( empty( $token_chunks[1] ) || empty( $token_chunks[2] ) ) {
24
				return false;
25
			}
26
			if ( $user_id != $token_chunks[2] ) {
27
				return false;
28
			}
29
			$token = "{$token_chunks[0]}.{$token_chunks[1]}";
30
		} else {
31
			$token = Jetpack_Options::get_option( 'blog_token' );
32
			if ( empty( $token ) ) {
33
				return false;
34
			}
35
		}
36
37
		return (object) array(
38
			'secret' => $token,
39
			'external_user_id' => (int) $user_id,
40
		);
41
	}
42
43
	/**
44
	 * This function mirrors Jetpack_Data::is_usable_domain() in the WPCOM codebase.
45
	 *
46
	 * @param $domain
47
	 * @param array $extra
48
	 *
49
	 * @return bool|WP_Error
50
	 */
51
	public static function is_usable_domain( $domain, $extra = array() ) {
52
53
		// If it's empty, just fail out.
54
		if ( ! $domain ) {
55
			return new WP_Error( 'fail_domain_empty', sprintf( __( 'Domain `%1$s` just failed is_usable_domain check as it is empty.', 'jetpack' ), $domain ) );
56
		}
57
58
		/**
59
		 * Skips the usuable domain check when connecting a site.
60
		 *
61
		 * Allows site administrators with domains that fail gethostname-based checks to pass the request to WP.com
62
		 *
63
		 * @since 4.1.0
64
		 *
65
		 * @param bool If the check should be skipped. Default false.
66
		 */
67
		if ( apply_filters( 'jetpack_skip_usuable_domain_check', false ) ) {
68
			return true;
69
		}
70
71
		// None of the explicit localhosts.
72
		$forbidden_domains = array(
73
			'wordpress.com',
74
			'localhost',
75
			'localhost.localdomain',
76
			'127.0.0.1',
77
			'local.wordpress.dev',         // VVV
78
			'local.wordpress-trunk.dev',   // VVV
79
			'src.wordpress-develop.dev',   // VVV
80
			'build.wordpress-develop.dev', // VVV
81
		);
82 View Code Duplication
		if ( in_array( $domain, $forbidden_domains ) ) {
83
			return new WP_Error( 'fail_domain_forbidden', sprintf( __( 'Domain `%1$s` just failed is_usable_domain check as it is in the forbidden array.', 'jetpack' ), $domain ) );
84
		}
85
86
		// No .dev or .local domains
87 View Code Duplication
		if ( preg_match( '#\.(dev|local)$#i', $domain ) ) {
88
			return new WP_Error( 'fail_domain_tld', sprintf( __( 'Domain `%1$s` just failed is_usable_domain check as it uses an invalid top level domain.', 'jetpack' ), $domain ) );
89
		}
90
91
		// No WPCOM subdomains
92 View Code Duplication
		if ( preg_match( '#\.wordpress\.com$#i', $domain ) ) {
93
			return new WP_Error( 'fail_subdomain_wpcom', sprintf( __( 'Domain `%1$s` just failed is_usable_domain check as it is a subdomain of WordPress.com.', 'jetpack' ), $domain ) );
94
		}
95
96
		// If PHP was compiled without support for the Filter module (very edge case)
97
		if ( ! function_exists( 'filter_var' ) ) {
98
			// Just pass back true for now, and let wpcom sort it out.
99
			return true;
100
		}
101
102
		// Check the IP to make sure it's pingable. We wrote our own DNS client because we can't rely on local DNS.
103
		$ips = gethostbyname_timeout( $domain . '.', '8.8.8.8', 10 );
104
105
		if ( false === $ips ) {
106
			return true; // probably a lookup timeout, assume everything's ok
107
		}
108
109
		if ( count( $ips ) == 0 ) {
110
			return false; // no public A-records
111
		}
112
113
		return true;
114
	}
115
116
	public static function gethostbyname_timeout( $domain, $dns, $timeout = 10 ) {
117
		// based off of http://www.php.net/manual/en/function.gethostbyaddr.php#46869
118
		// @ http://www.askapache.com/pub/php/gethostbyaddr.php
119
		// @ http://www.askapache.com/php/php-fsockopen-dns-udp.html
120
	
121
		$data = pack('n6', rand(10, 77), 0x0100, 1, 0, 0, 0);
122
		foreach (explode('.', $domain) as $bit) {
123
			$l = strlen($bit);
124
			$data .= chr($l) . $bit;
125
		}
126
		$data .= pack('n2', 1, 1);  // QTYPE=A, QCLASS=IN
127
128
		$errno = $errstr = 0;
129
		$fp = fsockopen( 'udp://' . $dns, 53, $errno, $errstr, $timeout );
130
		if (!$fp || !is_resource($fp)) return $errno;
131
132
		socket_set_timeout( $fp, $timeout );
133
		$requestsize = fwrite( $fp, $data );
134
135
		$max_rx = $requestsize * 3;
136
		$start = time();
137
		$response_data = '';
138
		$responsesize = 0;
139
		while ( $received < $max_rx && ( ( time() - $start ) < $timeout ) && ( $buf = fread( $fp, 1 ) ) !== false ) {
0 ignored issues
show
The variable $received does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
140
			$responsesize++;
141
			$response_data .= $buf;
142
		}
143
		$info = stream_get_meta_data( $fp );
144
		fclose( $fp );
145
146
		if ( $info[ 'timed_out' ] ) {
147
        	echo 'Connection timed out!';
148
			return false;
149
		}
150
151
		if ( ( time() - $start ) > $timeout ) {
152
			echo 'Response timed out!';
153
			return false;
154
		}
155
156
		// read answer header
157
		$ans_header = unpack( "nid/nspec/nqdcount/nancount/nnscount/narcount", substr( $response_data, 0, 12 ) );
158
159
		if ( ! $ans_header['ancount'] ) {
160
			echo 'No header records!';
161
			return false; // no answers!
162
		}
163
164
		// skip question part
165
		$offset = strlen( $domain ) + 4 + 2 + 1; // 4 => QTYPE + QCLASS, 2 => len, 1 => null terminator
166
167
		// loop and gather our A-records
168
		$loops = 0;
169
		$addresses = array();
170
		
171
		do {
172
			$record_header = unpack("ntype/nclass/Nttl/nlength/C4addr", substr( $response_data, 12 + $offset, 15 ) );
173
			$offset += $record_header['length'] + 12; // 4 => QTYPE + QCLASS, 4 = TTL, 2 = length
174
175
			if ( 1 != $record_header['class'] ) { 
176
				continue; // for some reason, it wasn't an A record
177
			}
178
179
			$addresses[] =  $record_header['addr1'] . '.' . $record_header['addr2'] . '.' . $record_header['addr3'] . '.' . $record_header['addr4'];
180
			$loops++;
181
		} while ( $record_header['length'] != 0 && $loops < 20 );
182
183
		return $addresses;
184
	}
185
186
	/**
187
	 * Returns true if the IP address passed in should not be in a reserved range, even if PHP says that it is.
188
	 * See: https://bugs.php.net/bug.php?id=66229 and https://github.com/php/php-src/commit/d1314893fd1325ca6aa0831101896e31135a2658
189
	 *
190
	 * This function mirrors Jetpack_Data::php_bug_66229_check() in the WPCOM codebase.
191
	 */
192
	public static function php_bug_66229_check( $ip ) {
193
		if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
194
			return false;
195
		}
196
197
		$ip_arr = array_map( 'intval', explode( '.', $ip ) );
198
199
		if ( 128 == $ip_arr[0] && 0 == $ip_arr[1] ) {
200
			return true;
201
		}
202
203
		if ( 191 == $ip_arr[0] && 255 == $ip_arr[1] ) {
204
			return true;
205
		}
206
207
		return false;
208
	}
209
}
210