diff --git a/include/class.auth.php b/include/class.auth.php
index 192c3af7079265b5d99527390ca497db9bd45fe2..0f13622e39661c46d166f09ec18f32f21576d62c 100644
--- a/include/class.auth.php
+++ b/include/class.auth.php
@@ -347,6 +347,38 @@ abstract class AuthenticationBackend {
         return false;
     }
 
+    /**
+     * Request the backend to update the password for a user. This method is
+     * the main entry for password updates so that password policies can be
+     * applied to the new password before passing the new password to the
+     * backend for updating.
+     *
+     * Throws:
+     * BadPassword — if password does not meet policy requirement
+     * PasswordUpdateFailed — if backend failed to update the password
+     */
+    function setPassword($user, $password, $current=false) {
+        PasswordPolicy::checkPassword($password, $current);
+        $rv = $this->syncPassword($user, $password);
+        if ($rv) {
+            $info = array('password' => $password, 'current' => $current);
+            Signal::send('auth.pwchange', $user, $info);
+        }
+        return $rv;
+    }
+
+    /**
+     * Request the backend to update the user's password with the password
+     * given. This method should only be used if the backend advertises
+     * supported password updates with the supportsPasswordChange() method.
+     *
+     * Returns:
+     * true if the password was successfully updated and false otherwise.
+     */
+    protected function syncPassword($user, $password) {
+        return false;
+    }
+
     function supportsPasswordReset() {
         return false;
     }
@@ -946,6 +978,14 @@ class osTicketAuthentication extends StaffAuthenticationBackend {
         }
     }
 
+    function supportsPasswordChange() {
+        return true;
+    }
+
+    function syncPassword($staff, $password) {
+        $staff->passwd = Passwd::hash($password);
+    }
+
 }
 StaffAuthenticationBackend::register('osTicketAuthentication');
 
@@ -1228,4 +1268,56 @@ class ClientAcctConfirmationTokenBackend extends UserAuthenticationBackend {
     }
 }
 UserAuthenticationBackend::register('ClientAcctConfirmationTokenBackend');
+
+// ----- Password Policy --------------------------------------
+
+class BadPassword extends Exception {}
+class PasswordUpdateFailed extends Exception {}
+
+abstract class PasswordPolicy {
+    static protected $registry = array();
+
+    /**
+     * Check a password and throw BadPassword with a meaningful message if
+     * the password cannot be accepted.
+     */
+    abstract function processPassword($new, $current);
+
+    static function checkPassword($new, $current) {
+        foreach (static::allActivePolicies() as $P) {
+            $P->processPassword($new, $current);
+        }
+    }
+
+    static function allActivePolicies() {
+        $policies = array();
+        foreach (static::$registry as $P) {
+            if (is_string($P) && class_exists($P))
+                $P = new $P();
+            if ($P instanceof PasswordPolicy)
+                $policies[] = $P;
+        }
+        return $policies;
+    }
+
+    static function register($policy) {
+        static::$registry[] = $policy;
+    }
+}
+
+class osTicketPasswordPolicy
+extends PasswordPolicy {
+    function processPassword($passwd, $current) {
+        if (strlen($passwd) < 6) {
+            throw new BadPassword(
+                __('Password must be at least 6 characters'));
+        }
+        // XXX: Changing case is technicall changing the password
+        if (0 === strcasecmp($passwd, $current)) {
+            throw new BadPassword(
+                __('New password MUST be different from the current password!'));
+        }
+    }
+}
+PasswordPolicy::register('osTicketPasswordPolicy');
 ?>
diff --git a/include/class.staff.php b/include/class.staff.php
index cc75aa0030c88a307d12df300456190a7d44ec0c..2654387c9896a379fb58afd50e2e1d2c838dd490 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -96,7 +96,13 @@ implements AuthenticatedUser, EmailContact {
     }
 
     function getAuthBackend() {
-        list($authkey, ) = explode(':', $this->getAuthKey());
+        list($bk, ) = explode(':', $this->getAuthKey());
+
+        // If administering a user other than yourself, fallback to the
+        // agent's declared backend, if any
+        if (!$bk && $this->backend)
+            $bk = $this->backend;
+
         return StaffAuthenticationBackend::getBackend($authkey);
     }
 
@@ -157,6 +163,34 @@ implements AuthenticatedUser, EmailContact {
                     && $this->passwd_change>($cfg->getPasswdResetPeriod()*30*24*60*60));
     }
 
+    function setPassword($new, $current=false) {
+        // Allow the backend to update the password. This is the preferred
+        // method as it allows for integration with password policies and
+        // also allows for remotely updating the password where possible and
+        // supported.
+        if (!($bk = $this->getAuthBackend())
+            || !$bk instanceof AuthBackend
+        ) {
+            // Fallback to osTicket authentication token udpates
+            $bk = new osTicketAuthentication();
+        }
+
+        // And now for the magic
+        if (!$bk->supportsPasswordChange()) {
+            throw new PasswordUpdateFailed(
+                __('Authentication backend does not support password updates'));
+        }
+        if (!$bk->setPassword($this, $new, $current)) {
+            // Backend should throw PasswordUpdateFailed directly
+            return false;
+        }
+
+        // Successfully updated authentication tokens
+        $this->change_passwd = 0;
+        $this->cancelResetTokens();
+        $this->passwdreset = SqlFunction::NOW();
+    }
+
     function canAccess($something) {
         if ($something instanceof RestrictedAccess)
             return $something->checkStaffPerm($this);
@@ -493,8 +527,6 @@ implements AuthenticatedUser, EmailContact {
 
             if(!$vars['passwd1'])
                 $errors['passwd1']=__('New password is required');
-            elseif($vars['passwd1'] && strlen($vars['passwd1'])<6)
-                $errors['passwd1']=__('Password must be at least 6 characters');
             elseif($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
                 $errors['passwd2']=__('Passwords do not match');
 
@@ -512,13 +544,24 @@ implements AuthenticatedUser, EmailContact {
                 $errors['cpasswd']=__('Current password is required');
             elseif(!$this->cmp_passwd($vars['cpasswd']))
                 $errors['cpasswd']=__('Invalid current password!');
-            elseif(!strcasecmp($vars['passwd1'], $vars['cpasswd']))
-                $errors['passwd1']=__('New password MUST be different from the current password!');
         }
 
         if($vars['default_signature_type']=='mine' && !$vars['signature'])
             $errors['default_signature_type'] = __("You don't have a signature");
 
+        // Update the user's password if requested
+        if ($vars['passwd1']) {
+            try {
+                $this->setPassword($vars['passwd1'], $vars['cpasswd']);
+            }
+            catch (BadPassword $ex) {
+                $errors['passwd1'] = $ex->getMessage();
+            }
+            catch (PasswordUpdateFailed $ex) {
+                // TODO: Add a warning banner or crash the update
+            }
+        }
+
         if($errors) return false;
 
         $_SESSION['staff:lang'] = null;
@@ -540,15 +583,6 @@ implements AuthenticatedUser, EmailContact {
         $this->default_paper_size = $vars['default_paper_size'];
         $this->lang = $vars['lang'];
 
-        if ($vars['passwd1']) {
-            $this->change_passwd = 0;
-            $this->passwdreset = SqlFunction::NOW();
-            $this->passwd = Passwd::hash($vars['passwd1']);
-            $info = array('password' => $vars['passwd1']);
-            Signal::send('auth.pwchange', $this, $info);
-            $this->cancelResetTokens();
-        }
-
         return $this->save();
     }