diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index e7056b813e47b2d0e95548fd77f1e35ac4b352cc..bc9647ede18c9b98973c77c64c22679a82fc97a0 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -870,3 +870,8 @@ a.refresh {
   color: #777;
   text-decoration: none;
 }
+table.padded tr > td,
+table.padded tr > th {
+  height: 20px;
+  padding-bottom: 5px;
+}
diff --git a/include/class.client.php b/include/class.client.php
index 5af39ac61b5f4a2c9b6ecead082fb31ee7f469bd..2fd702d56cab98c8e9d8bfe1bb55b2b856a73114 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -163,6 +163,7 @@ class TicketOwner extends  TicketUser {
 class  EndUser extends AuthenticatedUser {
 
     protected $user;
+    protected $_account = false;
 
     function __construct($user) {
         $this->user = $user;
@@ -228,11 +229,12 @@ class  EndUser extends AuthenticatedUser {
         return ($stats=$this->getTicketStats())?$stats['closed']:0;
     }
 
-    function hasAccount() {
-    }
-
     function getAccount() {
-        return ClientAccount::lookup(array('user_id'=>$this->getId()));
+        if ($this->_account === false)
+            $this->_account =
+                ClientAccount::lookup(array('user_id'=>$this->getId()));
+
+        return $this->_account;
     }
 
     private function getStats() {
@@ -269,10 +271,16 @@ class ClientAccountModel extends VerySimpleModel {
 
 class ClientAccount extends ClientAccountModel {
     var $_options = null;
+    var $timezone;
 
     const LOCKED = 0x0001;
     const PASSWD_RESET_REQUIRED = 0x0002;
 
+    function __onload() {
+        if ($this->get('timezone_id'))
+            $this->timezone = Timezone::getOffsetById($this->ht['timezone_id']);
+    }
+
     function checkPassword($password, $autoupdate=true) {
 
         /*bcrypt based password match*/
@@ -293,10 +301,75 @@ class ClientAccount extends ClientAccountModel {
         return true;
     }
 
+    function hasCurrentPassword($password) {
+        return $this->checkPassword($password, false);
+    }
+
     function forcePasswdReset() {
         $this->set('status', $this->get('status') | self::PASSWD_RESET_REQUIRED);
         $this->save();
     }
+
+    function cancelResetTokens() {
+        // TODO: Drop password-reset tokens from the config table for
+        //       this user id
+        $sql = 'DELETE FROM '.CONFIG_TABLE.' WHERE `namespace`="pwreset"
+            AND `value`='.db_input($this->getId());
+        db_query($sql, false);
+        unset($_SESSION['_client']['reset-token']);
+    }
+
+    function getInfo() {
+        $base = $this->ht;
+        $base['tz_offset'] = $this->timezone;
+        return $base;
+    }
+
+    function update($vars, &$errors) {
+        if($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd']) {
+
+            if(!$vars['passwd1'])
+                $errors['passwd1']='New password required';
+            elseif($vars['passwd1'] && strlen($vars['passwd1'])<6)
+                $errors['passwd1']='Must be at least 6 characters';
+            elseif($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
+                $errors['passwd2']='Password(s) do not match';
+
+            if (($rtoken = $_SESSION['_client']['reset-token'])) {
+                $_config = new Config('pwreset');
+                if ($_config->get($rtoken) != $this->getId())
+                    $errors['err'] =
+                        'Invalid reset token. Logout and try again';
+                elseif (!($ts = $_config->lastModified($rtoken))
+                        && ($cfg->getPwResetWindow() < (time() - strtotime($ts))))
+                    $errors['err'] =
+                        'Invalid reset token. Logout and try again';
+            }
+            elseif(!$vars['cpasswd'])
+                $errors['cpasswd']='Current password required';
+            elseif(!$this->hasCurrentPassword($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['timezone_id'])
+            $errors['timezone_id']='Time zone required';
+
+        if($errors) return false;
+
+        $this->set('timezone_id', $vars['timezone_id']);
+        $this->set('dst',isset($vars['dst'])?1:0);
+
+        if ($vars['passwd1']) {
+            $this->set('passwd', Passwd::hash($vars['passwd1']));
+            $info = array('password' => $vars['passwd1']);
+            Signal::send('auth.pwchange', $this, $info);
+            $this->cancelResetTokens();
+        }
+
+        return $this->save();
+    }
 }
 
 ?>
diff --git a/include/class.orm.php b/include/class.orm.php
index 951a4ffd425e88109f35c103aeed6e425bd2fb52..289ddaef7d8003640e7f24a35d459781bb72c2e4 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -97,6 +97,8 @@ class VerySimpleModel {
         }
     }
 
+    function __onload() {}
+
     static function _inspect() {
         if (!static::$meta['table'])
             throw new OrmConfigurationError(
@@ -366,7 +368,9 @@ class ModelInstanceIterator implements Iterator, ArrayAccess {
 
     function buildModel($row) {
         // TODO: Traverse to foreign keys
-        return new $this->model($row); # nolint
+        $model = new $this->model($row); # nolint
+        $model->__onload();
+        return $model;
     }
 
     function fillTo($index) {
diff --git a/include/client/profile.inc.php b/include/client/profile.inc.php
index a103ba1f7ec5f00ec1ec182b520ccfb2eaa8077b..d5651031cdaac3a0a5975edd862384a18d69bfe6 100644
--- a/include/client/profile.inc.php
+++ b/include/client/profile.inc.php
@@ -8,12 +8,83 @@ account
 </p>
 <form action="profile.php" method="post">
   <?php csrf_token(); ?>
-<table width="800">
+<table width="800" class="padded">
 <?php
 foreach ($user->getForms() as $f) {
     $f->render(false);
 }
+if ($acct = $thisclient->getAccount()) {
+    $info=$acct->getInfo();
+    $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 ?>
+<tr>
+    <td colspan="2">
+        <div><hr><h3>Preferences</h3>
+        </div>
+    </td>
+</tr>
+    <td>Time Zone:</td>
+    <td>
+        <select name="timezone_id" id="timezone_id">
+            <option value="0">&mdash; Select Time Zone &mdash;</option>
+            <?php
+            $sql='SELECT id, offset,timezone FROM '.TIMEZONE_TABLE.' ORDER BY id';
+            if(($res=db_query($sql)) && db_num_rows($res)){
+                while(list($id,$offset, $tz)=db_fetch_row($res)){
+                    $sel=($info['timezone_id']==$id)?'selected="selected"':'';
+                    echo sprintf('<option value="%d" %s>GMT %s - %s</option>',$id,$sel,$offset,$tz);
+                }
+            }
+            ?>
+        </select>
+        &nbsp;<span class="error"><?php echo $errors['timezone_id']; ?></span>
+    </td>
+</tr>
+<tr>
+    <td width="180">
+       Daylight Saving:
+    </td>
+    <td>
+        <input type="checkbox" name="dst" value="1" <?php echo $info['dst']?'checked="checked"':''; ?>>
+        Observe daylight saving
+        <em>(Current Time: <strong><?php echo Format::date($cfg->getDateTimeFormat(),Misc::gmtime(),$info['tz_offset'],$info['dst']); ?></strong>)</em>
+    </td>
+</tr>
+<tr>
+    <td colspan=2">
+        <div><hr><h3>Access Credentials</h3></div>
+    </td>
+</tr>
+<?php if (!isset($_SESSION['_client']['reset-token'])) { ?>
+<tr>
+    <td width="180">
+        Current Password:
+    </td>
+    <td>
+        <input type="password" size="18" name="cpasswd" value="<?php echo $info['cpasswd']; ?>">
+        &nbsp;<span class="error">&nbsp;<?php echo $errors['cpasswd']; ?></span>
+    </td>
+</tr>
+<?php } ?>
+<tr>
+    <td width="180">
+        New Password:
+    </td>
+    <td>
+        <input type="password" size="18" name="passwd1" value="<?php echo $info['passwd1']; ?>">
+        &nbsp;<span class="error">&nbsp;<?php echo $errors['passwd1']; ?></span>
+    </td>
+</tr>
+<tr>
+    <td width="180">
+        Confirm New Password:
+    </td>
+    <td>
+        <input type="password" size="18" name="passwd2" value="<?php echo $info['passwd2']; ?>">
+        &nbsp;<span class="error">&nbsp;<?php echo $errors['passwd2']; ?></span>
+    </td>
+</tr>
+<?php } ?>
 </table>
 <hr>
 <p style="text-align: center;">
diff --git a/profile.php b/profile.php
index e17bf690eb9241ff2b7bbad74efe8684bed447d2..47c100aef5fe46f44c1072e0248bc47058f9f047 100644
--- a/profile.php
+++ b/profile.php
@@ -23,7 +23,10 @@ $user = User::lookup($thisclient->getId());
 
 if ($user && $_POST) {
     $errors = array();
-    if ($user->updateInfo($_POST, $errors))
+    if ($acct = $thisclient->getAccount()) {
+       $acct->update($_POST, $errors);
+    }
+    if (!$errors && $user->updateInfo($_POST, $errors))
         Http::redirect('tickets.php');
 }