diff --git a/admin/admin.php b/admin/admin.php index 3e8b3b5..cc565f3 100644 --- a/admin/admin.php +++ b/admin/admin.php @@ -232,10 +232,43 @@ public function sanitize_ip_addresses( $val ) { */ private function validate_ip_address( $ip_address ) { - return filter_var( $ip_address, FILTER_VALIDATE_IP ); + $ip = trim(explode("#",$ip_address)[0]); // allow for comments after ip + + if ($this->is_ip($ip)) { + return trim($ip_address); + } + else { + return ""; + } } + /** + * Is it a valid IP address? v4/v6 with subnet range. + * + * @param string $ip_address IP Address to check. + * + * @return bool True if its a valid IP address. + */ + private function is_ip( $ip_address ) { + // very basic validation of ranges. + if ( strpos( $ip_address, '/' ) ) { + $ip_parts = explode( '/', $ip_address ); + if ( empty( $ip_parts[1] ) || ! is_numeric( $ip_parts[1] ) || strlen( $ip_parts[1] ) > 3 ) { + return false; + } + $ip_address = $ip_parts[0]; + } + + // confirm IP part is a valid IPv6 or IPv4 IP. + if ( empty( $ip_address ) || ! inet_pton( stripslashes( $ip_address ) ) ) { + return false; + } + + return true; + } + + /** * Password Protected Section */ @@ -282,9 +315,9 @@ public function password_protected_password_field() { */ public function password_protected_allowed_ip_addresses_field() { - echo '' . get_option( 'password_protected_allowed_ip_addresses' ) . ''; + echo '' . get_option( 'password_protected_allowed_ip_addresses' ) . ''; - echo '
' . esc_html__( 'Enter one IP address per line.', 'password-protected' ); + echo '
' . esc_html__( 'Enter one IP address per line. (IP/CIDR netmask notation is allowed eg. 127.0.0.0/24, comments are allowed after IP eg. 127.0.0.1 #Comment)', 'password-protected' ); if ( isset( $_SERVER['REMOTE_ADDR'] ) ) { echo ' ' . esc_html( sprintf( __( 'Your IP is address %s.', 'password-protected' ), $_SERVER['REMOTE_ADDR'] ) ); } diff --git a/password-protected.php b/password-protected.php index b446eab..a472dd7 100644 --- a/password-protected.php +++ b/password-protected.php @@ -214,6 +214,27 @@ public function allow_users( $bool ) { } + /** + * Check if a given ip is in a network. + * Source: https://gist.github.com/tott/7684443 + * + * @param string $ip IP to check in IPV4 format eg. 127.0.0.1. + * @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed. + * @return boolean true if the ip is in this range / false if not. + */ + public static function ip_in_range( $ip, $range ) { + if ( strpos( $range, '/' ) === false ) { + $range .= '/32'; + } + // $range is in IP/CIDR format eg 127.0.0.1/24 + list( $range, $netmask ) = explode( '/', $range, 2 ); + $range_decimal = ip2long( $range ); + $ip_decimal = ip2long( $ip ); + $wildcard_decimal = pow( 2, ( 32 - $netmask ) ) - 1; + $netmask_decimal = ~ $wildcard_decimal; + return ( ( $ip_decimal & $netmask_decimal ) === ( $range_decimal & $netmask_decimal ) ); + } + /** * Allow IP Addresses * @@ -226,8 +247,18 @@ public function allow_ip_addresses( $bool ) { $ip_addresses = $this->get_allowed_ip_addresses(); - if ( isset( $_SERVER['REMOTE_ADDR'] ) && in_array( $_SERVER['REMOTE_ADDR'], $ip_addresses ) ) { - $bool = false; + if ( isset( $_SERVER['REMOTE_ADDR'] ) ) { + + $remote_ip = $_SERVER['REMOTE_ADDR']; + + // iterate through the allow list. + foreach ( $ip_addresses as $line ) { + $line = trim(explode("#",$line)[0]); // allow for comments after ip + if ( $this->ip_in_range( $remote_ip, $line ) ) { + $bool = false; + } + } + } return $bool;