--- modules/user/user.module	2011-02-11 08:01:09.000000000 +0100
+++ modules/user/user.module	2011-02-11 09:46:17.000000000 +0100
@@ -446,6 +446,34 @@
   }
 }
 
+function user_check_password($password, $account) {
+  $hash = hash('md5', $password, FALSE);
+  return ($hash && $account->pass == $hash);
+}
+
+/**
+ * Form validation handler for the current password on the user_account_form().
+ */
+function user_validate_current_pass(&$form, &$form_state) {
+  global $user;
+
+  $account = $form['#user'];
+  foreach ($form_state['values']['current_pass_required_values'] as $key => $name) {
+    // This validation only works for required textfields (like mail) or
+    // form values like password_confirm that have their own validation
+    // that prevent them from being empty if they are changed.
+    if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) {
+      $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $user);
+      if ($current_pass_failed) {
+        form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name)));
+        form_set_error($key);
+      }
+      // We only need to check the password once.
+      break;
+    }
+  }
+}
+
 /**
  * Generate a random alphanumeric password.
  */
@@ -929,7 +957,7 @@
     'title' => 'Request new password',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('user_pass'),
-    'access callback' => 'user_is_anonymous',
+    'access callback' => TRUE,
     'type' => MENU_LOCAL_TASK,
     'file' => 'user.pages.inc',
   );
@@ -1486,6 +1514,7 @@
       '#maxlength' => USERNAME_MAX_LENGTH,
       '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'),
       '#required' => TRUE,
+      '#weight' => -10,
     );
   }
   $form['account']['mail'] = array('#type' => 'textfield',
@@ -1500,6 +1529,36 @@
       '#description' => t('To change the current user password, enter the new password in both fields.'),
       '#size' => 25,
     );
+    // To skip the current password field, the user must have logged in via a
+    // one-time link and have the token in the URL.
+    $pass_reset = isset($_SESSION['pass_reset_' . $uid]) && isset($_GET['pass-reset-token']) && ($_GET['pass-reset-token'] == $_SESSION['pass_reset_' . $uid]);
+    $protected_values = array();
+    $current_pass_description = '';
+    // The user may only change their own password without their current
+    // password if they logged in via a one-time login link.
+    if (!$pass_reset) {
+      $protected_values['mail'] = $form['account']['mail']['#title'];
+      $protected_values['pass'] = t('Password');
+      $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.'))));
+      $current_pass_description = t('Enter your current password to change the %mail or %pass. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new));
+    }
+    global $user;
+    // The user must enter their current password to change to a new one.
+    if ($user->uid == $uid) {
+      $form['account']['current_pass_required_values'] = array(
+        '#type' => 'value',
+        '#value' => $protected_values,
+      );
+      $form['account']['current_pass'] = array(
+        '#type' => 'password',
+        '#title' => t('Current password'),
+        '#size' => 25,
+        '#access' => !empty($protected_values),
+        '#description' => $current_pass_description,
+        '#weight' => -5,
+      );
+      $form['#validate'][] = 'user_validate_current_pass';
+    }
   }
   elseif (!variable_get('user_email_verification', TRUE) || $admin) {
     $form['account']['pass'] = array(
@@ -1627,6 +1686,9 @@
   if (isset($edit['roles'])) {
     $edit['roles'] = array_filter($edit['roles']);
   }
+  // Unset current password to avoid it being saved.
+  unset($edit['current_pass_required_values']);
+  unset($edit['current_pass']);
 }
 
 /**
--- modules/user/user.pages.inc	2011-02-11 08:01:16.000000000 +0100
+++ modules/user/user.pages.inc	2011-02-11 09:40:21.000000000 +0100
@@ -29,6 +29,8 @@
  * @see user_pass_submit()
  */
 function user_pass() {
+  global $user;
+
   $form['name'] = array(
     '#type' => 'textfield',
     '#title' => t('Username or e-mail address'),
@@ -36,6 +38,16 @@
     '#maxlength' => max(USERNAME_MAX_LENGTH, EMAIL_MAX_LENGTH),
     '#required' => TRUE,
   );
+  // Allow logged in users to request this also.
+  if ($user->uid > 0) {
+    $form['name']['#type'] = 'value';
+    $form['name']['#value'] = $user->mail;
+    $form['mail'] = array(
+      '#prefix' => '<p>',
+      '#markup' =>  t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the e-mail.', array('%email' => $user->mail)),
+      '#suffix' => '</p>',
+    );
+  }
   $form['submit'] = array('#type' => 'submit', '#value' => t('E-mail new password'));
 
   return $form;
@@ -79,6 +91,47 @@
   return;
 }
 
+function drupal_random_bytes($count) {
+  // $random_state does not use drupal_static as it stores random bytes.
+  static $random_state, $bytes;
+  // Initialize on the first call. The contents of $_SERVER includes a mix of
+  // user-specific and system information that varies a little with each page.
+  if (!isset($random_state)) {
+    $random_state = print_r($_SERVER, TRUE);
+    if (function_exists('getmypid')) {
+      // Further initialize with the somewhat random PHP process ID.
+      $random_state .= getmypid();
+    }
+    $bytes = '';
+  }
+  if (strlen($bytes) < $count) {
+    // /dev/urandom is available on many *nix systems and is considered the
+    // best commonly available pseudo-random source.
+    if ($fh = @fopen('/dev/urandom', 'rb')) {
+      // PHP only performs buffered reads, so in reality it will always read
+      // at least 4096 bytes. Thus, it costs nothing extra to read and store
+      // that much so as to speed any additional invocations.
+      $bytes .= fread($fh, max(4096, $count));
+      fclose($fh);
+    }
+    // If /dev/urandom is not available or returns no bytes, this loop will
+    // generate a good set of pseudo-random bytes on any system.
+    // Note that it may be important that our $random_state is passed
+    // through hash() prior to being rolled into $output, that the two hash()
+    // invocations are different, and that the extra input into the first one -
+    // the microtime() - is prepended rather than appended. This is to avoid
+    // directly leaking $random_state via the $output stream, which could
+    // allow for trivial prediction of further "random" numbers.
+    while (strlen($bytes) < $count) {
+      $random_state = hash('sha256', microtime() . mt_rand() . $random_state);
+      $bytes .= hash('sha256', mt_rand() . $random_state, TRUE);
+    }
+  }
+  $output = substr($bytes, 0, $count);
+  $bytes = substr($bytes, $count);
+  return $output;
+}
+
 /**
  * Menu callback; process one time login link and redirects to the user page on success.
  */
@@ -117,7 +170,10 @@
           // user, which invalidates further use of the one-time login link.
           user_authenticate_finalize($form_state['values']);
           drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to login. Please change your password.'));
-          drupal_goto('user/'. $user->uid .'/edit');
+          // Let the user's password be changed without the current password check.
+          $token = md5(drupal_random_bytes(55));
+          $_SESSION['pass_reset_' . $user->uid] = $token;
+          drupal_goto('user/' . $user->uid . '/edit', array('pass-reset-token' => $token));
         }
         else {
           $form['message'] = array('#value' => t('<p>This is a one-time login for %user_name and will expire on %expiration_date.</p><p>Click on this button to login to the site and change your password.</p>', array('%user_name' => $account->name, '%expiration_date' => format_date($timestamp + $timeout))));
@@ -292,6 +348,11 @@
   user_module_invoke('submit', $form_state['values'], $account, $category);
   user_save($account, $form_state['values'], $category);
 
+  if ($category == 'account' && !empty($form_state['values']['pass'])) {
+    // Remove the password reset tag since a new password was saved.
+    unset($_SESSION['pass_reset_'. $account->uid]);
+  }
+
   // Clear the page cache because pages can contain usernames and/or profile information:
   cache_clear_all();
 
