P BY `lockout_host`, DATE(`lockout_start_gmt`) SQL, date( 'Y-m-d H:i:s', $last_seen ) ), ARRAY_A ); ITSEC_Modules::set_setting( 'core', 'last_seen_lockout', ITSEC_Core::get_current_time_gmt() ); if ( is_array( $lockouts ) && $lockouts ) { $insert = "INSERT INTO {$wpdb->base_prefix}itsec_dashboard_lockouts (`ip`, `time`, `count`) VALUES "; $prepare = []; foreach ( $lockouts as $lockout ) { $insert .= '(%s, %s, %d),'; $prepare[] = $lockout['h']; $prepare[] = $lockout['d']; $prepare[] = $lockout['c']; } $insert[ strlen( $insert ) - 1 ] = ';'; $wpdb->query( $wpdb->prepare( $insert, $prepare ) ); } } /** * Gets a list of top blocked IPs in a given period. * * @param int $number Number of IPs to return. * @param int $after Find IPs locked out after this time. * @param int $before Find IPs locked out before this time. * * @return Result */ public function get_top_blocked_ips( int $number, int $after, int $before ): Result { global $wpdb; $results = $wpdb->get_results( $wpdb->prepare( <<base_prefix}itsec_dashboard_lockouts WHERE `time` > %s AND `time` < %s GROUP BY `ip` ORDER BY c DESC LIMIT %d SQL, date( 'Y-m-d H:i:s', $after ), date( 'Y-m-d H:i:s', $before ), $number ), ARRAY_A ); if ( $wpdb->last_error ) { return Result::error( new WP_Error( 'itsec.lockout.top-blocked-ips.db-error', $wpdb->last_error ) ); } return Result::success( array_map( function ( array $result ) { return [ 'ip' => $result['ip'], 'count' => (int) $result['c'], ]; }, $results ) ); } /** * Register verbs for Sync. * * @since 3.6.0 * * @param Ithemes_Sync_API $api API object. */ public function register_sync_verbs( $api ) { $api->register( 'itsec-get-lockouts', 'Ithemes_Sync_Verb_ITSEC_Get_Lockouts', dirname( __FILE__ ) . '/sync-verbs/itsec-get-lockouts.php' ); $api->register( 'itsec-release-lockout', 'Ithemes_Sync_Verb_ITSEC_Release_Lockout', dirname( __FILE__ ) . '/sync-verbs/itsec-release-lockout.php' ); $api->register( 'itsec-get-temp-whitelist', 'Ithemes_Sync_Verb_ITSEC_Get_Temp_Whitelist', dirname( __FILE__ ) . '/sync-verbs/itsec-get-temp-whitelist.php' ); $api->register( 'itsec-set-temp-whitelist', 'Ithemes_Sync_Verb_ITSEC_Set_Temp_Whitelist', dirname( __FILE__ ) . '/sync-verbs/itsec-set-temp-whitelist.php' ); } /** * Filter to add verbs to the response for the itsec-get-everything verb. * * @since 3.6.0 * * @param array $verbs of verbs. * * @return array Array of verbs. */ public function register_sync_get_everything_verbs( $verbs ) { $verbs['lockout'][] = 'itsec-get-lockouts'; $verbs['lockout'][] = 'itsec-get-temp-whitelist'; return $verbs; } /** * Register modules that will use the lockout service. * * @return void */ public function register_modules() { /** * Filter the available lockout modules. * * @param array $lockout_modules Each lockout module should be an array containing 'type', 'reason' and * 'period' options. The type is a unique string referring to the type of lockout. * 'reason' is a human readable label describing the reason for the lockout. * 'period' is the number of days to check for lockouts to decide if the host * should be permanently banned. Additionally, the 'user' and 'host' options instruct * security to wait for that many temporary lockout events to occur before executing * the lockout. */ $this->lockout_modules = apply_filters( 'itsec_lockout_modules', $this->lockout_modules ); } /** * Get all the registered lockout modules. * * @return array */ public function get_lockout_modules() { return $this->lockout_modules; } /** * Get lockout details. * * @param int $id * @param string $return * * @return Lockout\Lockout|array|false * @throws Exception */ public function get_lockout( $id, $return = ARRAY_A ) { global $wpdb; $data = wp_cache_get( $id, 'itsec-lockouts' ); if ( ! $data ) { $results = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$wpdb->base_prefix}itsec_lockouts` WHERE `lockout_id` = %d", $id ), ARRAY_A ); if ( ! is_array( $results ) || ! isset( $results[0] ) ) { return false; } $data = $results[0]; wp_cache_add( $id, $data, 'itsec-lockouts' ); } if ( $return === OBJECT ) { return $this->hydrate_lockout_entity( $id, $data ); } return $data; } /** * Process clearing lockouts on view log page * * @since 4.0 * * @param int $id * * @return bool true on success or false */ public function release_lockout( $id = 0 ) { global $wpdb; if ( ! $id ) { return false; } return (bool) $wpdb->update( $wpdb->base_prefix . 'itsec_lockouts', array( 'lockout_active' => 0, ), array( 'lockout_id' => (int) $id, ) ); } /** * Register the lockout notification. * * @param array $notifications * * @return array */ public function register_notification( $notifications ) { $notifications['lockout'] = array( 'subject_editable' => true, 'recipient' => ITSEC_Notification_Center::R_USER_LIST, 'schedule' => ITSEC_Notification_Center::S_NONE, 'optional' => true, ); return $notifications; } /** * Get the strings for the lockout notification. * * @return array */ public function notification_strings() { return array( 'label' => __( 'Site Lockouts', 'better-wp-security' ), 'description' => __( 'Various modules send emails to notify you when a user or IP address is locked out of your website.', 'better-wp-security' ), 'subject' => __( 'Site Lockout Notification', 'better-wp-security' ), ); } /** * Sends an email to notify site admins of lockouts * * @since 4.0 * * @param Lockout\Context $context * @param string $host_expiration when the host login expires * @param string $user_expiration when the user lockout expires * @param string $reason the reason for the lockout to show to the user * * @return void */ private function send_lockout_email( Lockout\Context $context, $host_expiration, $user_expiration, $reason ) { $nc = ITSEC_Core::get_notification_center(); if ( ! $nc || ! $nc->is_notification_enabled( 'lockout' ) ) { return; } $lockouts = array(); $show_remove_ip_ban_message = false; $show_remove_lockout_message = false; if ( ( $context instanceof Lockout\User_Context && $user_id = $context->get_user_id() ) || ( $context instanceof Lockout\Host_Context && $context->is_user_limit_triggered() && $user_id = $context->get_login_user_id() ) ) { $show_remove_lockout_message = true; $lockouts[] = array( 'type' => 'user', 'id' => get_userdata( $user_id )->user_login, 'until' => $user_expiration, 'reason' => $reason, ); } if ( ( $context instanceof Lockout\Username_Context && $username = $context->get_username() ) || ( $context instanceof Lockout\Host_Context && $context->is_user_limit_triggered() && $username = $context->get_login_username() ) ) { $lockouts[] = array( 'type' => 'username', 'id' => $username, 'until' => $user_expiration, 'reason' => $reason, ); } if ( $context instanceof Lockout\Host_Context ) { if ( false === $host_expiration ) { $host_expiration = __( 'Permanently', 'better-wp-security' ); $show_remove_ip_ban_message = true; } else { $show_remove_lockout_message = true; } $lockouts[] = array( 'type' => 'host', 'id' => $context->get_host(), 'until' => $host_expiration, 'reason' => $reason, ); } $mail = $nc->mail(); $mail->add_header( esc_html__( 'Site Lockout Notification', 'better-wp-security' ), esc_html__( 'Site Lockout Notification', 'better-wp-security' ), false, sprintf( esc_html__( '%s lockout notification', 'better-wp-security' ), $mail->get_display_url() ), ); $mail->add_lockouts_table( $lockouts ); if ( $show_remove_lockout_message ) { $mail->add_text( __( 'Release lockouts from the Active Lockouts section of the Security -> Dashboard page.', 'better-wp-security' ) ); $mail->add_button( __( 'Visit Dashboard', 'better-wp-security' ), ITSEC_Mail::filter_admin_page_url( network_admin_url( 'admin.php?page=itsec-dashboard' ) ) ); } if ( $show_remove_ip_ban_message ) { $mail->add_text( __( 'Release the permanently banned IP from the Banned IPs dashboard card.', 'better-wp-security' ) ); $mail->add_button( __( 'Visit Dashboard', 'better-wp-security' ), ITSEC_Mail::filter_admin_page_url( network_admin_url( 'admin.php?page=itsec-dashboard' ) ) ); } $mail->add_footer(); $subject = $mail->prepend_site_url_to_subject( $nc->get_subject( 'lockout' ) ); $subject = apply_filters( 'itsec_lockout_email_subject', $subject ); $mail->set_subject( $subject, false ); $nc->send( 'lockout', $mail ); } public function filter_entry_for_list_display( $entry, $code, $data ) { $entry['module_display'] = esc_html__( 'Lockout', 'better-wp-security' ); if ( 'whitelisted-host-triggered-blacklist' === $code ) { $entry['description'] = esc_html__( 'Authorized IP Triggered Ban Conditions', 'better-wp-security' ); } elseif ( 'host-triggered-blacklist' === $code ) { $entry['description'] = esc_html__( 'IP Triggered Ban Conditions', 'better-wp-security' ); } elseif ( 'whitelisted-host-triggered-host-lockout' === $code ) { $entry['description'] = esc_html__( 'Authorized IP Triggered IP Lockout', 'better-wp-security' ); } elseif ( 'host-lockout' === $code ) { if ( isset( $data[0] ) ) { $entry['description'] = sprintf( wp_kses( __( 'IP Lockout: %s', 'better-wp-security' ), array( 'code' => array() ) ), $data[0] ); } else { $entry['description'] = esc_html__( 'IP Lockout', 'better-wp-security' ); } } elseif ( 'whitelisted-host-triggered-user-lockout' === $code ) { $entry['description'] = esc_html__( 'Authorized IP Triggered User Lockout', 'better-wp-security' ); } elseif ( 'user-lockout' === $code ) { if ( isset( $data[0] ) ) { $user = get_user_by( 'id', $data[0] ); } if ( isset( $user ) && false !== $user ) { $entry['description'] = sprintf( wp_kses( __( 'User Lockout: %s', 'better-wp-security' ), array( 'code' => array() ) ), $user->user_login ); } else { $entry['description'] = esc_html__( 'User Lockout', 'better-wp-security' ); } } elseif ( 'whitelisted-host-triggered-username-lockout' === $code ) { $entry['description'] = esc_html__( 'Authorized IP Triggered Username Lockout', 'better-wp-security' ); } elseif ( 'username-lockout' === $code ) { if ( isset( $data[0] ) ) { $entry['description'] = sprintf( wp_kses( __( 'Username Lockout: %s', 'better-wp-security' ), array( 'code' => array() ) ), $data[0] ); } else { $entry['description'] = esc_html__( 'Username Lockout', 'better-wp-security' ); } } return $entry; } }