PluginProbe
DoLogin Security / trunk
DoLogin Security vtrunk
5.0.10 4.8.3 trunk 1.0 1.1 1.1.1 1.2 1.2.1 1.2.2 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 1.6 All 64 releases
dologin / src / ip.cls.php

ip.cls.php in DoLogin Security trunk, at src/ip.cls.php

261 lines 6.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * IP class
5 *
6 * @since 1.0
7 */
8
9 namespace dologin;
10
11 defined( 'WPINC' ) || exit;
12
13 class IP extends Instance {
14
15 private $_visitor_geo_data = array();
16
17 public static $PREFIX_SET = array(
18 'continent',
19 'continent_code',
20 'country',
21 'country_code',
22 'subdivision',
23 'subdivision_code',
24 'city',
25 'postal',
26 );
27
28 /**
29 * Get visitor's IP
30 *
31 * @since 1.0
32 * @access public
33 */
34 public static function me() {
35 $_ip = '';
36
37 if ( ! $_ip ) {
38 $_ip = ! empty( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : false;
39 }
40
41 if ( strpos( $_ip, ',' ) ) {
42 $_ip = explode( ',', $_ip );
43 $_ip = trim( $_ip[0] );
44 }
45
46 return preg_replace( '/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $_ip );
47 }
48
49 /**
50 * Get geolocation info of visitor IP
51 *
52 * @since 1.0
53 * @access public
54 */
55 public static function geo( $ip = false ) {
56 if ( ! $ip ) {
57 $ip = self::me();
58 }
59 $ip = trim( (string) $ip );
60 if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
61 return array( 'ip' => $ip );
62 }
63
64 $cache_key = 'dologin_geo_' . md5( $ip );
65 $cached = get_transient( $cache_key );
66 if ( is_array( $cached ) ) {
67 return $cached;
68 }
69
70 $response = wp_safe_remote_get(
71 'https://www.doapi.us/ip/' . rawurlencode( $ip ) . '/json',
72 array(
73 'timeout' => 3,
74 'redirection' => 0,
75 'limit_response_size' => 32768,
76 'sslverify' => true,
77 )
78 );
79
80 $data = array();
81 if ( ! is_wp_error( $response ) && 200 === (int) wp_remote_retrieve_response_code( $response ) ) {
82 $decoded = json_decode( wp_remote_retrieve_body( $response ), true );
83 if ( is_array( $decoded ) ) {
84 $data = $decoded;
85 }
86 }
87
88 // Build geo data
89 $geo_list = array( 'ip' => $ip );
90 foreach ( self::$PREFIX_SET as $tag ) {
91 $geo_list[ $tag ] = isset( $data[ $tag ] ) && is_scalar( $data[ $tag ] ) ? sanitize_text_field( trim( (string) $data[ $tag ] ) ) : false;
92 }
93 set_transient( $cache_key, $geo_list, $data ? HOUR_IN_SECONDS : 5 * MINUTE_IN_SECONDS );
94
95 return $geo_list;
96 }
97
98 /**
99 * Match an IP address against an exact or segmented wildcard rule.
100 *
101 * @since 4.6.5
102 */
103 public static function matches_ip_rule( $visitor, $rule ) {
104 $visitor = strtolower( trim( (string) $visitor ) );
105 $rule = strtolower( trim( (string) $rule ) );
106 if ( ! filter_var( $visitor, FILTER_VALIDATE_IP ) || '' === $rule ) {
107 return false;
108 }
109 if ( false === strpos( $rule, '*' ) ) {
110 $rule_bytes = filter_var( $rule, FILTER_VALIDATE_IP ) ? inet_pton( $rule ) : false;
111 return false !== $rule_bytes && inet_pton( $visitor ) === $rule_bytes;
112 }
113
114 $is_v4 = (bool) filter_var( $visitor, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 );
115 $separator = $is_v4 ? '.' : ':';
116 $replacement = str_replace( '*', '0', $rule );
117 $flag = $is_v4 ? FILTER_FLAG_IPV4 : FILTER_FLAG_IPV6;
118 if ( ! filter_var( $replacement, FILTER_VALIDATE_IP, $flag ) ) {
119 return false;
120 }
121
122 if ( ! $is_v4 ) {
123 $unpacked = unpack( 'n8', inet_pton( $visitor ) );
124 $visitor_parts = array_map( 'dechex', array_values( $unpacked ) );
125 $rule_parts = self::expand_ipv6_rule( $rule );
126 if ( false === $rule_parts ) {
127 return false;
128 }
129 } else {
130 $visitor_parts = explode( $separator, $visitor );
131 $rule_parts = explode( $separator, $rule );
132 }
133 if ( count( $visitor_parts ) !== count( $rule_parts ) ) {
134 return false;
135 }
136
137 foreach ( $visitor_parts as $index => $part ) {
138 if ( '*' === $rule_parts[ $index ] ) {
139 continue;
140 }
141 if ( $is_v4 ) {
142 if ( (int) $part !== (int) $rule_parts[ $index ] ) {
143 return false;
144 }
145 } elseif ( ltrim( $part, '0' ) !== ltrim( $rule_parts[ $index ], '0' ) ) {
146 return false;
147 }
148 }
149
150 return true;
151 }
152
153 /**
154 * Expand a validated IPv6 wildcard rule to eight segments.
155 */
156 private static function expand_ipv6_rule( $rule ) {
157 if ( substr_count( $rule, '::' ) > 1 ) {
158 return false;
159 }
160 if ( false !== strpos( $rule, '::' ) ) {
161 $halves = explode( '::', $rule, 2 );
162 $left = '' === $halves[0] ? array() : explode( ':', $halves[0] );
163 $right = '' === $halves[1] ? array() : explode( ':', $halves[1] );
164 $fill = 8 - count( $left ) - count( $right );
165 if ( $fill < 1 ) {
166 return false;
167 }
168 $parts = array_merge( $left, array_fill( 0, $fill, '0' ), $right );
169 } else {
170 $parts = explode( ':', $rule );
171 }
172 if ( 8 !== count( $parts ) ) {
173 return false;
174 }
175 foreach ( $parts as $part ) {
176 if ( '*' !== $part && ! preg_match( '/^[0-9a-f]{1,4}$/', $part ) ) {
177 return false;
178 }
179 }
180 return $parts;
181 }
182
183 /**
184 * Validate if hit the list
185 *
186 * @since 1.0
187 * @access public
188 */
189 public function maybe_hit_rule( $list, $ip_only = false ) {
190 if ( ! $this->_visitor_geo_data ) {
191 $this->_visitor_geo_data = $ip_only ? array( 'ip' => self::me() ) : self::geo();
192 }
193
194 foreach ( $list as $v ) {
195 // Drop comments
196 if ( strpos( $v, '#' ) !== false ) {
197 $v = trim( substr( $v, 0, strpos( $v, '#' ) ) );
198 }
199
200 if ( ! $v ) {
201 continue;
202 }
203
204 $v = explode( ',', $v );
205
206 // Go through each rule
207 foreach ( $v as $v2 ) {
208 $negative_match = false;
209
210 $rule_prefix = false;
211 if ( false !== strpos( $v2, ':' ) ) {
212 $prefix_candidate = trim( substr( $v2, 0, strpos( $v2, ':' ) ) );
213 $prefix_key = rtrim( $prefix_candidate, '!' );
214 $rule_prefix = 'ip' === $prefix_key || in_array( $prefix_key, self::$PREFIX_SET, true );
215 }
216
217 if ( ! $rule_prefix ) { // Treat values without a known key prefix as IP addresses, including IPv6.
218 $curr_k = 'ip';
219 } else {
220 list($curr_k, $v2) = explode( ':', $v2, 2 );
221 $curr_k = trim( $curr_k );
222 if ( substr( $curr_k, -1 ) === '!' ) {
223 $negative_match = true;
224 $curr_k = trim( substr( $curr_k, 0, -1 ) );
225 }
226 }
227
228 $v2 = trim( $v2 );
229
230 // Invalid rule
231 if ( ! $v2 ) {
232 continue 2;
233 }
234
235 // Rule set not match
236 if ( empty( $this->_visitor_geo_data[ $curr_k ] ) ) {
237 continue 2;
238 }
239
240 $v2 = strtolower( $v2 );
241 $visitor_v = strtolower( $this->_visitor_geo_data[ $curr_k ] );
242 $visitor_v = trim( $visitor_v );
243
244 $matched = 'ip' === $curr_k ? self::matches_ip_rule( $visitor_v, $v2 ) : $visitor_v === $v2;
245
246 if ( ! $negative_match && ! $matched ) {
247 continue 2;
248 }
249
250 if ( $negative_match && $matched ) {
251 continue 2;
252 }
253 }
254
255 return true;
256 }
257
258 return false;
259 }
260 }
261