Index: includes/class.wp-auth.php
===================================================================
--- includes/class.wp-auth.php	(revision 357)
+++ includes/class.wp-auth.php	(working copy)
@@ -148,28 +148,12 @@
 	 */
 	function validate_auth_cookie( $cookie = null, $scheme = 'auth' )
 	{
-		if ( empty( $cookie ) ) {
-			foreach ( $this->cookies[$scheme] as $_scheme_cookie ) {
-				// Take the first cookie of type scheme that exists
-				if ( !empty( $_COOKIE[$_scheme_cookie['name']] ) ) {
-					$cookie = $_COOKIE[$_scheme_cookie['name']];
-					break;
-				}
-			}
-		}
-		
-		if ( !$cookie ) {
+		if ( ! $cookie_elements = $this->parse_auth_cookie( $cookie, $scheme ) ) {
+			do_action( 'auth_cookie_malformed', $cookie );
 			return false;
 		}
+		extract( $cookie_elements );
 
-		$cookie_elements = explode( '|', $cookie );
-		if ( count( $cookie_elements ) != 3 ) {
-			do_action( 'auth_cookie_malformed', $cookie, $scheme );
-			return false;
-		}
-
-		list( $username, $expiration, $hmac ) = $cookie_elements;
-
 		$expired = $expiration;
 
 		// Allow a grace period for POST and AJAX requests
@@ -193,8 +177,8 @@
 			$pass_frag = substr( $user->user_pass, 8, 4 );
 		}
 
-		$key  = call_user_func( backpress_get_option( 'hash_function_name' ), $username . $pass_frag . '|' . $expiration, $scheme );
-		$hash = hash_hmac( 'md5', $username . '|' . $expiration, $key );
+		$key  = call_user_func( backpress_get_option( 'hash_function_name' ), $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
+		$hash = hash_hmac( 'md5', $username . '|' . $expiration . '|' . $token, $key );
 	
 		if ( $hmac != $hash ) {
 			do_action( 'auth_cookie_bad_hash', $cookie_elements );
@@ -201,6 +185,11 @@
 			return false;
 		}
 
+		if ( ! $this->verify_session_token( $user, $token ) ) {
+			do_action( 'auth_cookie_bad_session_token', $cookie_elements );
+			return false;
+		}
+
 		do_action( 'auth_cookie_valid', $cookie_elements, $user );
 
 		return $user->ID;
@@ -215,9 +204,11 @@
 	 *
 	 * @param int $user_id User ID
 	 * @param int $expiration Cookie expiration in seconds
+	 * @param string $scheme The cookie scheme to use: auth, secure_auth, or logged_in
+	 * @param string $token User's session token to use for this cookie
 	 * @return string Authentication cookie contents
 	 */
-	function generate_auth_cookie( $user_id, $expiration, $scheme = 'auth' )
+	function generate_auth_cookie( $user_id, $expiration, $scheme = 'auth', $token = '' )
 	{
 		$user = $this->users->get_user( $user_id );
 		if ( !$user || is_wp_error( $user ) ) {
@@ -224,15 +215,18 @@
 			return $user;
 		}
 
+		if ( empty( $token ) )
+			$token = $this->create_session_token( $user, $expiration );
+
 		$pass_frag = '';
 		if ( 1 < WP_AUTH_COOKIE_VERSION ) {
 			$pass_frag = substr( $user->user_pass, 8, 4 );
 		}
 
-		$key  = call_user_func( backpress_get_option( 'hash_function_name' ), $user->user_login . $pass_frag . '|' . $expiration, $scheme );
-		$hash = hash_hmac('md5', $user->user_login . '|' . $expiration, $key);
+		$key  = call_user_func( backpress_get_option( 'hash_function_name' ), $user->user_login . '|' . $pass_frag . '|' . $expiration . '|' . $token, $scheme );
+		$hash = hash_hmac('md5', $user->user_login . '|' . $expiration . '|' . $token, $key);
 
-		$cookie = $user->user_login . '|' . $expiration . '|' . $hash;
+		$cookie = $user->user_login . '|' . $expiration . '|' . $token . '|' . $hash;
 
 		return apply_filters( 'auth_cookie', $cookie, $user_id, $expiration, $scheme );
 	}
@@ -264,8 +258,10 @@
 
 		$expire = absint( $expire );
 
+		$token = $this->create_session_token( $user_id, $expiration );
+
 		foreach ( $this->cookies[$scheme] as $_cookie ) {
-			$cookie = $this->generate_auth_cookie( $user_id, $expiration, $scheme );
+			$cookie = $this->generate_auth_cookie( $user_id, $expiration, $scheme, $token );
 			if ( is_wp_error( $cookie ) ) {
 				return $cookie;
 			}
@@ -302,4 +298,73 @@
 		}
 		unset( $_scheme, $_scheme_cookies );
 	}
+
+	function create_session_token( $user, $expiration ) {
+		if ( ! is_object( $user ) )
+			$user = $this->users->get_user( $user );
+
+		$sessions = ! empty( $user->session_tokens ) ? $user->session_tokens : array();
+
+		$time = time();
+		foreach ( $sessions as $v => $e ) {
+			if ( $e < $time )
+				unset( $sessions[ $v ] );
+		}
+
+		$token = WP_Pass::generate_password( 40, false );
+		$verifier = hash( 'sha256', $token );
+		$sessions[ $verifier ] = $expiration;
+
+		$GLOBALS['wp_users_object']->update_meta( array( 'id' => $user->ID, 'meta_key' => 'session_tokens', 'meta_value' => $sessions ) );
+
+		return $token;
+	}
+
+	function verify_session_token( $user, $token ) {
+		// Cached user may not have appropriate meta set
+		if ( ! isset( $user->session_tokens ) )
+			$user = $GLOBALS['wp_users_object']->append_meta( $user );
+		$verifier = hash( 'sha256', $token );
+		return isset( $user->session_tokens[ $verifier ] ) && $user->session_tokens[ $verifier ] >= time();
+	}
+
+	function destroy_current_session() {
+		$cookie = $this->parse_auth_cookie( '', 'logged_in' );
+		if ( ! empty( $cookie['token'] ) ) {
+			$user = $this->get_current_user();
+			$verifier = hash( 'sha256', $cookie['token'] );
+			$sessions = ! empty( $user->session_tokens ) ? $user->session_tokens : array();
+			unset( $sessions[ $verifier ] );
+
+			$time = time();
+			foreach ( $sessions as $v => $e ) {
+				if ( $e < $time )
+					unset( $sessions[ $v ] );
+			}
+
+			$GLOBALS['wp_users_object']->update_meta( array( 'id' => $user->ID, 'meta_key' => 'session_tokens', 'meta_value' => $sessions ) );
+		}
+	}
+
+	function parse_auth_cookie( $cookie = '', $scheme = 'auth' ) {
+		if ( empty( $cookie ) ) {
+			foreach ( $this->cookies[$scheme] as $_scheme_cookie ) {
+				// Take the first cookie of type scheme that exists
+				if ( !empty( $_COOKIE[$_scheme_cookie['name']] ) ) {
+					$cookie = $_COOKIE[$_scheme_cookie['name']];
+					break;
+				}
+			}
+		}
+		
+		if ( ! $cookie )
+			return false;
+
+		$cookie_elements = explode( '|', $cookie );
+		if ( count( $cookie_elements ) != 4 )
+			return false;
+
+		list( $username, $expiration, $token, $hmac ) = $cookie_elements;
+		return compact( 'username', 'expiration', 'token', 'hmac' );
+	}
 }
