diff --git a/include/class.auth.php b/include/class.auth.php
index d984cb3cb1296051bf0cf4bbe9190984017ca70a..a0abe4ed4468455e9867730cd1d46a9d20fd3405 100644
--- a/include/class.auth.php
+++ b/include/class.auth.php
@@ -1315,7 +1315,35 @@ abstract class PasswordPolicy {
     static function register($policy) {
         static::$registry[] = $policy;
     }
+
+    static function cleanSessions($model, $user=null) {
+        $criteria = array();
+
+        switch (true) {
+            case ($model instanceof Staff):
+                $criteria['user_id'] = $model->getId();
+
+                if ($user && ($model->getId() == $user->getId()))
+                    array_push($criteria,
+                        Q::not(array('session_id' => $user->session->session_id)));
+                break;
+            case ($model instanceof User):
+                $regexp = '_auth\|.*"user";[a-z]+:[0-9]+:{[a-z]+:[0-9]+:"id";[a-z]+:'.$model->getId();
+                $criteria['user_id'] = 0;
+                $criteria['session_data__regex'] = $regexp;
+
+                if ($user)
+                    array_push($criteria,
+                        Q::not(array('session_id' => $user->session->session_id)));
+                break;
+            default:
+                return false;
+        }
+
+        return SessionData::objects()->filter($criteria)->delete();
+    }
 }
+Signal::connect('auth.clean', array('PasswordPolicy', 'cleanSessions'));
 
 class osTicketPasswordPolicy
 extends PasswordPolicy {
diff --git a/include/class.client.php b/include/class.client.php
index 83684cae3e51155a81f6c39a4ae95821429e399f..11b500e4aef370bf60846024ada0cf8cf645df55 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -416,7 +416,7 @@ class ClientAccount extends UserAccount {
         global $cfg;
 
         // FIXME: Updates by agents should go through UserAccount::update()
-        global $thisstaff;
+        global $thisstaff, $thisclient;
         if ($thisstaff)
             return parent::update($vars, $errors);
 
@@ -474,6 +474,8 @@ class ClientAccount extends UserAccount {
             Signal::send('auth.pwchange', $this->getUser(), $info);
             $this->cancelResetTokens();
             $this->clearStatus(UserAccountStatus::REQUIRE_PASSWD_RESET);
+            // Clean sessions
+            Signal::send('auth.clean', $this->getUser(), $thisclient);
         }
 
         return $this->save();
diff --git a/include/class.config.php b/include/class.config.php
index 66a3232b2518fd13954b7ef924d8daa19461344d..004a1e8e8a44d2be9fdcc1cde54acd0a03300d73 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -176,6 +176,20 @@ extends VerySimpleModel {
             $this->updated = SqlFunction::NOW();
         return parent::save($this->dirty || $refetch);
     }
+
+    // Clean password reset tokens that have expired
+    static function cleanPwResets() {
+        global $cfg;
+
+        if (!$cfg || !($period = $cfg->getPwResetWindow())) // In seconds
+            return false;
+
+        return ConfigItem::objects()
+             ->filter(array(
+                'namespace' => 'pwreset',
+                'updated__lt' => SqlFunction::NOW()->minus(SqlInterval::SECOND($period)),
+            ))->delete();
+    }
 }
 
 class OsticketConfig extends Config {
diff --git a/include/class.cron.php b/include/class.cron.php
index ee67f7f31aac6a742a411a61358726636d22eba3..f259d60b3a5fb87c825faeb371daaccfe92b9884 100644
--- a/include/class.cron.php
+++ b/include/class.cron.php
@@ -56,6 +56,11 @@ class Cron {
         DbSessionBackend::cleanup();
     }
 
+    function CleanPwResets() {
+        require_once(INCLUDE_DIR.'class.config.php');
+        ConfigItem::cleanPwResets();
+    }
+
     function MaybeOptimizeTables() {
         // Once a week on a 5-minute cron
         $chance = rand(1,2000);
@@ -106,6 +111,7 @@ class Cron {
         self::TicketMonitor();
         self::PurgeLogs();
         self::CleanExpiredSessions();
+        self::CleanPwResets();
         // Run file purging about every 10 cron runs
         if (mt_rand(1, 9) == 4)
             self::CleanOrphanedFiles();
diff --git a/include/class.forms.php b/include/class.forms.php
index 1915257b7faf6d3c38480545781f7aca7ffbe821..c6183e30163e0ba995dceae22da76027007c722f 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -3166,6 +3166,7 @@ class FileUploadField extends FormField {
     static $widget = 'FileUploadWidget';
 
     protected $attachments;
+    protected $files;
 
     static function getFileTypes() {
         static $filetypes;
@@ -3333,7 +3334,7 @@ class FileUploadField extends FormField {
         return ($ext && is_array($allowed) && in_array(".$ext", $allowed));
     }
 
-    function getFiles() {
+    function getAttachments() {
         if (!isset($this->attachments) && ($a = $this->getAnswer())
             && ($e = $a->getEntry()) && ($e->get('id'))
         ) {
@@ -3349,6 +3350,18 @@ class FileUploadField extends FormField {
         $this->attachments = $att;
     }
 
+    function getFiles() {
+        if (!isset($this->files)) {
+            $files = array();
+            foreach ($this->getAttachments() as $a) {
+                if ($a && ($f=$a->getFile()))
+                    $files[] = $f;
+            }
+            $this->files = $files;
+        }
+        return $this->files;
+    }
+
     function getConfiguration() {
         $config = parent::getConfiguration();
         $_types = self::getFileTypes();
@@ -3406,8 +3419,8 @@ class FileUploadField extends FormField {
     // array. Then, inspect the difference between the files actually
     // attached to this field
     function to_database($value) {
-        $this->getFiles();
-        if (isset($this->attachments)) {
+        $this->getAttachments();
+        if (isset($this->attachments) && $this->attachments) {
             $this->attachments->keepOnlyFileIds($value);
         }
         return JsonDataEncoder::encode($value);
@@ -3436,21 +3449,22 @@ class FileUploadField extends FormField {
     function toString($value) {
         $files = array();
         foreach ($this->getFiles() as $f) {
-            $files[] = $f->file->name;
+            $files[] = $f->name;
         }
         return implode(', ', $files);
     }
 
     function db_cleanup($field=false) {
-        // Delete associated attachments from the database, if any
-        $this->getFiles();
-        if (isset($this->attachments)) {
+        if ($this->getAttachments()) {
             $this->attachments->deleteAll();
         }
     }
 
     function asVar($value, $id=false) {
-        return new FileFieldAttachments($this->getFiles());
+        if (($attachments = $this->getAttachments()))
+            $attachments = $attachments->all();
+
+        return new FileFieldAttachments($attachments ?: array());
     }
     function asVarType() {
         return 'FileFieldAttachments';
@@ -3489,26 +3503,30 @@ class FileUploadField extends FormField {
 }
 
 class FileFieldAttachments {
-    var $files;
+    var $attachments;
 
-    function __construct($files) {
-        $this->files = $files;
+    function __construct($attachments) {
+        $this->attachments = $attachments;
     }
 
     function __toString() {
         $files = array();
-        foreach ($this->files as $f) {
-            $files[] = $f->file->name;
+        foreach ($this->getAttachments() as $a) {
+            $files[] = $a->getFilename();
         }
         return implode(', ', $files);
     }
 
+    function getAttachments() {
+        return $this->attachments ?: array();
+    }
+
     function getVar($tag) {
         switch ($tag) {
         case 'names':
             return $this->__toString();
         case 'files':
-            throw new OOBContent(OOBContent::FILES, $this->files->all());
+            throw new OOBContent(OOBContent::FILES, $this->getAttachments());
         }
     }
 
@@ -4377,7 +4395,7 @@ class FileUploadWidget extends Widget {
         $config = $this->field->getConfiguration();
         $name = $this->field->getFormName();
         $id = substr(md5(spl_object_hash($this)), 10);
-        $attachments = $this->field->getFiles();
+        $attachments = $this->field->getAttachments();
         $mimetypes = array_filter($config['__mimetypes'],
             function($t) { return strpos($t, '/') !== false; }
         );
@@ -4546,21 +4564,22 @@ class FreeTextField extends FormField {
     }
 
     function getAttachments() {
-
         if (!isset($this->attachments))
             $this->attachments = GenericAttachments::forIdAndType($this->get('id'), 'I');
 
-        return $this->attachments;
+        return $this->attachments ?: array();
     }
 
     function getFiles() {
-
-        if (!($attachments = $this->getAttachments()))
-            return array();
-
-        return $attachments->all();
+        if (!isset($this->files)) {
+            $files = array();
+            if (($attachments=$this->getAttachments()))
+                foreach ($attachments->all() as $a)
+                    $files[] = $a->getFile();
+            $this->files = $files;
+        }
+        return $this->files;
     }
-
 }
 
 class FreeTextWidget extends Widget {
@@ -4582,12 +4601,10 @@ class FreeTextWidget extends Widget {
             echo Format::viewableImages($config['content']); ?></div>
         </div>
         <?php
-        if (($attachments = $this->field->getFiles()) && count($attachments)) { ?>
+        if (($attachments = $this->field->getAttachments()) && count($attachments)) { ?>
             <section class="freetext-files">
             <div class="title"><?php echo __('Related Resources'); ?></div>
-            <?php foreach ($attachments as $attach) {
-                $filename = Format::htmlchars($attach->getFilename());
-                ?>
+            <?php foreach ($attachments->all() as $attach) { ?>
                 <div class="file">
                 <a href="<?php echo $attach->file->getDownloadUrl(); ?>"
                     target="_blank" download="<?php echo $filename; ?>"
diff --git a/include/class.mailer.php b/include/class.mailer.php
index db18b71d3288e6bc253233f62fcd216b1ed43436..dfd65b2ab254fabbb7005ef55e30add8921ac85b 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -445,10 +445,10 @@ class Mailer {
 
         // Add in extra attachments, if any from template variables
         if ($message instanceof TextWithExtras
-            && ($files = $message->getFiles())
+            && ($attachments = $message->getAttachments())
         ) {
-            foreach ($files as $F) {
-                $file = $F->getFile();
+            foreach ($attachments as $a) {
+                $file = $a->getFile();
                 $mime->addAttachment($file->getData(),
                     $file->getType(), $file->getName(), false);
             }
diff --git a/include/class.staff.php b/include/class.staff.php
index 608b6b7c0c6e843dc711d33d6b5179d0bbec70a3..a9024227332507d9dce728d05ed8bf0512886f08 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -230,6 +230,8 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
     }
 
     function setPassword($new, $current=false) {
+        global $thisstaff;
+
         // 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
@@ -254,6 +256,9 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
         $this->cancelResetTokens();
         $this->passwdreset = SqlFunction::NOW();
 
+        // Clean sessions
+        Signal::send('auth.clean', $this, $thisstaff);
+
         return $rv;
     }
 
diff --git a/include/class.user.php b/include/class.user.php
index f6660d16f0123397d884c64b4b676a55af411f70..53a8627c3bd4d95e319d86e26d9e1f8498f5a4d4 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -1143,6 +1143,8 @@ class UserAccount extends VerySimpleModel {
 
     function setPassword($new) {
         $this->set('passwd', Passwd::hash($new));
+        // Clean sessions
+        Signal::send('auth.clean', $this->getUser());
     }
 
     protected function sendUnlockEmail($template) {
diff --git a/include/class.variable.php b/include/class.variable.php
index 8b346e0cd387483494dc2f245211a16ac75a5784..6a1517b9426a551afe416cb8441f713f37aff421 100644
--- a/include/class.variable.php
+++ b/include/class.variable.php
@@ -383,8 +383,8 @@ class TextWithExtras {
         return $this->text;
     }
 
-    function getFiles() {
-        return $this->extras[OOBContent::FILES];
+    function getAttachments() {
+        return $this->extras[OOBContent::FILES] ?: array();
     }
 }
 
diff --git a/include/staff/tasks.inc.php b/include/staff/tasks.inc.php
index c8983aca53190d4b300f0894590ae3978c51d224..8fc2b0534807a8a48386a33cd45f7f4de266dc48 100644
--- a/include/staff/tasks.inc.php
+++ b/include/staff/tasks.inc.php
@@ -119,19 +119,19 @@ if ($filters)
 // Impose visibility constraints
 // ------------------------------------------------------------
 // -- Open and assigned to me
-$visibility = array(
+$visibility = Q::any(
     new Q(array('flags__hasbit' => TaskModel::ISOPEN, 'staff_id' => $thisstaff->getId()))
 );
 // -- Routed to a department of mine
 if (!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts()))
-    $visibility[] = new Q(array('dept_id__in' => $depts));
+    $visibility->add(new Q(array('dept_id__in' => $depts)));
 // -- Open and assigned to a team of mine
 if (($teams = $thisstaff->getTeams()) && count(array_filter($teams)))
-    $visibility[] = new Q(array(
+    $visibility->add(new Q(array(
         'team_id__in' => array_filter($teams),
         'flags__hasbit' => TaskModel::ISOPEN
-    ));
-$tasks->filter(Q::any($visibility));
+    )));
+$tasks->filter(new Q($visibility));
 
 // Add in annotations
 $tasks->annotate(array(