diff --git a/include/class.config.php b/include/class.config.php
index c1d1ce691078ad0109e7dfb849d36e036e119e44..a1dd1de245f0a5245396a2cf81369ced37f1e8eb 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -856,6 +856,20 @@ class OsticketConfig extends Config {
          ));
     }
 
+    function getLogo($site) {
+        $id = $this->get("{$site}_logo_id", false);
+        return ($id) ? AttachmentFile::lookup($id) : null;
+    }
+    function getClientLogo() {
+        return $this->getLogo('client');
+    }
+    function getLogoId($site) {
+        return $this->get("{$site}_logo_id", false);
+    }
+    function getClientLogoId() {
+        return $this->getLogoId('client');
+    }
+
     function updatePagesSettings($vars, &$errors) {
 
         $f=array();
@@ -863,13 +877,33 @@ class OsticketConfig extends Config {
         $f['offline_page_id'] = array('type'=>'int',   'required'=>1, 'error'=>'required');
         $f['thank-you_page_id'] = array('type'=>'int',   'required'=>1, 'error'=>'required');
 
+        if ($_FILES['logo']) {
+            $error = false;
+            list($logo) = AttachmentFile::format($_FILES['logo']);
+            if (!$logo)
+                ; // Pass
+            elseif ($logo['error'])
+                $errors['logo'] = $logo['error'];
+            elseif (!($id = AttachmentFile::uploadLogo($logo, $error)))
+                $errors['logo'] = 'Unable to upload logo image. '.$error;
+        }
+
         if(!Validator::process($f, $vars, $errors) || $errors)
             return false;
 
+        if (isset($vars['delete-logo']))
+            foreach ($vars['delete-logo'] as $id)
+                if (($vars['selected-logo'] != $id)
+                        && ($f = AttachmentFile::lookup($id)))
+                    $f->delete();
+
         return $this->updateAll(array(
             'landing_page_id' => $vars['landing_page_id'],
             'offline_page_id' => $vars['offline_page_id'],
             'thank-you_page_id' => $vars['thank-you_page_id'],
+            'client_logo_id' => (
+                (is_numeric($vars['selected-logo']) && $vars['selected-logo'])
+                ? $vars['selected-logo'] : false),
            ));
     }
 
diff --git a/include/class.file.php b/include/class.file.php
index dcfc1e8d1d8b95d91079318adce5630dc77ef5cf..d4ea01f33fafa1b7a1e6bf0aa3d4076121dd7821 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -91,6 +91,18 @@ class AttachmentFile {
         return $this->ht['hash'];
     }
 
+    function lastModified() {
+        return $this->ht['created'];
+    }
+
+    /**
+     * Retrieve a hash that can be sent to scp/file.php?h= in order to
+     * download this file
+     */
+    function getDownloadHash() {
+        return strtolower($this->getHash() . md5($this->getId().session_id().$this->getHash()));
+    }
+
     function open() {
         return new AttachmentChunkedData($this->id);
     }
@@ -126,7 +138,19 @@ class AttachmentFile {
 
 
     function display() {
-       
+
+        // Thanks, http://stackoverflow.com/a/1583753/1025836
+        $last_modified = strtotime($this->lastModified());
+        header("Last-Modified: ".gmdate(DATE_RFC822, $last_modified)." GMT", false);
+        header('ETag: "'.$this->getHash().'"');
+        header('Cache-Control: private, max-age=3600');
+        header('Expires: ' . date(DATE_RFC822, time() + 3600) . ' GMT');
+        header('Pragma: private');
+        if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified ||
+            @trim($_SERVER['HTTP_IF_NONE_MATCH']) == $this->getHash()) {
+                header("HTTP/1.1 304 Not Modified");
+                exit();
+        }
 
         header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream'));
         header('Content-Length: '.$this->getSize());
@@ -141,7 +165,7 @@ class AttachmentFile {
         header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
         header('Cache-Control: public');
         header('Content-Type: '.($this->getType()?$this->getType():'application/octet-stream'));
-    
+
         $filename=basename($this->getName());
         $user_agent = strtolower ($_SERVER['HTTP_USER_AGENT']);
         if ((is_integer(strpos($user_agent,'msie'))) && (is_integer(strpos($user_agent,'win')))) {
@@ -149,7 +173,7 @@ class AttachmentFile {
         }else{
             header('Content-Disposition: attachment; filename='.$filename.';' );
         }
-        
+
         header('Content-Transfer-Encoding: binary');
         header('Content-Length: '.$this->getSize());
         $this->sendData();
@@ -157,12 +181,13 @@ class AttachmentFile {
     }
 
     /* Function assumes the files types have been validated */
-    function upload($file) {
-        
+    function upload($file, $ft='T') {
+
         if(!$file['name'] || $file['error'] || !is_uploaded_file($file['tmp_name']))
             return false;
 
         $info=array('type'=>$file['type'],
+                    'filetype'=>$ft,
                     'size'=>$file['size'],
                     'name'=>$file['name'],
                     'hash'=>MD5(MD5_FILE($file['tmp_name']).time()),
@@ -172,15 +197,49 @@ class AttachmentFile {
         return AttachmentFile::save($info);
     }
 
+    function uploadLogo($file, &$error, $aspect_ratio=3) {
+        /* Borrowed in part from
+         * http://salman-w.blogspot.com/2009/04/crop-to-fit-image-using-aspphp.html
+         */
+        if (!extension_loaded('gd'))
+            return self::upload($file, 'L');
+
+        $source_path = $file['tmp_name'];
+
+        list($source_width, $source_height, $source_type) = getimagesize($source_path);
+
+        switch ($source_type) {
+            case IMAGETYPE_GIF:
+            case IMAGETYPE_JPEG:
+            case IMAGETYPE_PNG:
+                break;
+            default:
+                // TODO: Return an error
+                $error = 'Invalid image file type';
+                return false;
+        }
+
+        $source_aspect_ratio = $source_width / $source_height;
+
+        if ($source_aspect_ratio >= $aspect_ratio)
+            return self::upload($file, 'L');
+
+        $error = 'Image is too square. Upload a wider image';
+        return false;
+    }
+
     function save($file) {
 
         if(!$file['hash'])
             $file['hash']=MD5(MD5($file['data']).time());
         if(!$file['size'])
             $file['size']=strlen($file['data']);
-        
+        if(!$file['filetype'])
+            $file['filetype'] = 'T';
+
         $sql='INSERT INTO '.FILE_TABLE.' SET created=NOW() '
             .',type='.db_input($file['type'])
+            .',ft='.db_input($file['filetype'])
             .',size='.db_input($file['size'])
             .',name='.db_input(Format::file_name($file['name']))
             .',hash='.db_input($file['hash']);
@@ -208,11 +267,11 @@ class AttachmentFile {
     function lookup($id) {
 
         $id = is_numeric($id)?$id:AttachmentFile::getIdByHash($id);
-        
+
         return ($id && ($file = new AttachmentFile($id)) && $file->getId()==$id)?$file:null;
     }
 
-    /* 
+    /*
       Method formats http based $_FILE uploads - plus basic validation.
       @restrict - make sure file type & size are allowed.
      */
@@ -234,7 +293,7 @@ class AttachmentFile {
         foreach($attachments as $i => &$file) {
             //skip no file upload "error" - why PHP calls it an error is beyond me.
             if($file['error'] && $file['error']==UPLOAD_ERR_NO_FILE) {
-                unset($attachments[$i]); 
+                unset($attachments[$i]);
                 continue;
             }
 
@@ -263,7 +322,7 @@ class AttachmentFile {
      * canned-response, or faq point to any more.
      */
     /* static */ function deleteOrphans() {
-        
+
         $sql = 'DELETE FROM '.FILE_TABLE.' WHERE id NOT IN ('
                 # DISTINCT implies sort and may not be necessary
                 .'SELECT DISTINCT(file_id) FROM ('
@@ -273,16 +332,27 @@ class AttachmentFile {
                     .' UNION ALL '
                     .'SELECT file_id FROM '.FAQ_ATTACHMENT_TABLE
                 .') still_loved'
-            .')';
+            .') AND `ft` = "T"';
 
         db_query($sql);
-        
+
         //Delete orphaned chuncked data!
         AttachmentChunkedData::deleteOrphans();
-    
+
         return true;
 
     }
+
+    /* static */
+    function allLogos() {
+        $sql = 'SELECT id FROM '.FILE_TABLE.' WHERE ft="L"
+            ORDER BY created';
+        $logos = array();
+        $res = db_query($sql);
+        while (list($id) = db_fetch_row($res))
+            $logos[] = AttachmentFile::lookup($id);
+        return $logos;
+    }
 }
 
 /**
@@ -328,11 +398,11 @@ class AttachmentChunkedData {
     }
 
     function deleteOrphans() {
-            
+
         $sql = 'DELETE c.* FROM '.FILE_CHUNK_TABLE.' c '
              . ' LEFT JOIN '.FILE_TABLE.' f ON(f.id=c.file_id) '
              . ' WHERE f.id IS NULL';
-        
+
         return db_query($sql)?db_affected_rows():0;
     }
 }
diff --git a/include/client/header.inc.php b/include/client/header.inc.php
index d4c90ff1fcec0a0bb4ba179dd675c738cbc5a06e..781df6e59568d7b20e3c926c046f6ea5214b7ae5 100644
--- a/include/client/header.inc.php
+++ b/include/client/header.inc.php
@@ -20,7 +20,10 @@ header("Content-Type: text/html; charset=UTF-8\r\n");
 <body>
     <div id="container">
         <div id="header">
-            <a id="logo" href="<?php echo ROOT_PATH; ?>index.php" title="Support Center"><img src="<?php echo ASSETS_PATH; ?>images/logo.png" border=0 alt="Support Center"></a>
+            <a id="logo" href="<?php echo ROOT_PATH; ?>index.php"
+            title="Support Center"><img src="logo.php" border=0 alt="<?php
+                echo $ost->getConfig()->getTitle(); ?>"
+                style="height: 5em"></a>
             <p>
              <?php
              if($thisclient && is_object($thisclient) && $thisclient->isValid()) {
diff --git a/include/staff/settings-pages.inc.php b/include/staff/settings-pages.inc.php
index b58a148d4ae6173fc7ed9f688ba302dd0aa2f23e..4be25699801138abf371d387138a1d6e2918b03b 100644
--- a/include/staff/settings-pages.inc.php
+++ b/include/staff/settings-pages.inc.php
@@ -3,7 +3,8 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config)
 $pages = Page::getPages();
 ?>
 <h2>Site Pages</h2>
-<form action="settings.php?t=pages" method="post" id="save">
+<form action="settings.php?t=pages" method="post" id="save"
+    enctype="multipart/form-data">
 <?php csrf_token(); ?>
 <input type="hidden" name="t" value="pages" >
 <table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2">
@@ -66,8 +67,117 @@ $pages = Page::getPages();
         </tr>
     </tbody>
 </table>
+<table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2">
+    <thead>
+        <tr>
+            <th colspan="2">
+                <h4>Logos</h4>
+                <em>System Default Logo</em>
+            </th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+        <td colspan="2">
+                <label style="display:block">
+                <input type="radio" name="selected-logo" value="0"
+                    style="margin-left: 1em"
+                    <?php if (!$ost->getConfig()->getClientLogoId())
+                        echo 'checked="checked"'; ?>/>
+                <img src="../assets/default/images/logo.png"
+                    alt="Default Logo" valign="middle"
+                    style="box-shadow: 0 0 0.5em rgba(0,0,0,0.5);
+                        margin: 0.5em; height: 5em"/>
+                </label>
+        </td></tr>
+        <tr>
+            <th colspan="2">
+                <em>Use a custom logo &mdash; Use a delete checkbox to
+                remove the logo from the system</em>
+            </th>
+        </tr>
+        <tr><td colspan="2">
+            <?php
+            $current = $ost->getConfig()->getClientLogoId();
+            foreach (AttachmentFile::allLogos() as $logo) { ?>
+                <div>
+                <label>
+                <input type="radio" name="selected-logo"
+                    style="margin-left: 1em" value="<?php
+                    echo $logo->getId(); ?>" <?php
+                    if ($logo->getId() == $current)
+                        echo 'checked="checked"'; ?>/>
+                <img src="image.php?h=<?php echo $logo->getDownloadHash(); ?>"
+                    alt="Custom Logo" valign="middle"
+                    style="box-shadow: 0 0 0.5em rgba(0,0,0,0.5);
+                        margin: 0.5em; height: 5em;"/>
+                </label>
+                <?php if ($logo->getId() != $current) { ?>
+                <label>
+                <input type="checkbox" name="delete-logo[]" value="<?php
+                    echo $logo->getId(); ?>"/> Delete
+                </label>
+                <?php } ?>
+                </div>
+            <?php } ?>
+            <br/>
+            <b>Upload a new logo:</b>
+            <input type="file" name="logo[]" size="30" value="" />
+            <font class="error"><br/><?php echo $errors['logo']; ?></font>
+        </td>
+        </tr>
+    </tbody>
+</table>
 <p style="padding-left:250px;">
-    <input class="button" type="submit" name="submit" value="Save Changes">
+    <input class="button" type="submit" name="submit-button" value="Save Changes">
     <input class="button" type="reset" name="reset" value="Reset Changes">
 </p>
 </form>
+
+<div style="display:none;" class="dialog" id="confirm-action">
+    <h3>Please Confirm</h3>
+    <a class="close" href="">&times;</a>
+    <hr/>
+    <p class="confirm-action" id="delete-confirm">
+        <font color="red"><strong>Are you sure you want to DELETE selected
+        logos?</strong></font>
+        <br/><br/>Deleted logos CANNOT be recovered.
+    </p>
+    <div>Please confirm to continue.</div>
+    <hr style="margin-top:1em"/>
+    <p class="full-width">
+        <span class="buttons" style="float:left">
+            <input type="button" value="No, Cancel" class="close">
+        </span>
+        <span class="buttons" style="float:right">
+            <input type="button" value="Yes, Do it!" class="confirm">
+        </span>
+     </p>
+    <div class="clear"></div>
+</div>
+
+<script type="text/javascript">
+$(function() {
+    $('#save input:submit.button').bind('click', function(e) {
+        var formObj = $('#save');
+        if ($('input:checkbox:checked', formObj).length) {
+            e.preventDefault();
+            $('.dialog#confirm-action').undelegate('.confirm');
+            $('.dialog#confirm-action').delegate('input.confirm', 'click', function(e) {
+                e.preventDefault();
+                $('.dialog#confirm-action').hide();
+                $('#overlay').hide();
+                formObj.submit();
+                return false;
+            });
+            $('#overlay').show();
+            $('.dialog#confirm-action .confirm-action').hide();
+            $('.dialog#confirm-action p#delete-confirm')
+            .show()
+            .parent('div').show().trigger('click');
+            return false;
+        }
+        else return true;
+    });
+});
+</script>
diff --git a/login.php b/login.php
index 8ca3901055291f961390319c0ca54e99c9d29add..789938980ff451cdcc72920d2b9f88080dcc7a47 100644
--- a/login.php
+++ b/login.php
@@ -2,7 +2,7 @@
 /*********************************************************************
     login.php
 
-    Client Login 
+    Client Login
 
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2013 osTicket
diff --git a/logo.php b/logo.php
new file mode 100644
index 0000000000000000000000000000000000000000..7972433548d8b6165aa31938a51911ce2e8683b1
--- /dev/null
+++ b/logo.php
@@ -0,0 +1,32 @@
+<?php
+/*********************************************************************
+    logo.php
+
+    Simple logo to facilitate serving a customized client-side logo from
+    osTicet. The logo is configurable in Admin Panel -> Settings -> Pages
+
+    Peter Rotich <peter@osticket.com>
+    Jared Hancock <jared@osticket.com>
+    Copyright (c)  2006-2013 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+
+// Don't update the session for inline image fetches
+if (!function_exists('noop')) { function noop() {} }
+session_set_save_handler('noop','noop','noop','noop','noop','noop');
+define('DISABLE_SESSION', true);
+
+require('client.inc.php');
+
+if (($logo = $ost->getConfig()->getClientLogo())) {
+    $logo->display();
+} else {
+    header('Location: '.ASSETS_PATH.'images/logo.png');
+}
+
+?>
diff --git a/scp/image.php b/scp/image.php
new file mode 100644
index 0000000000000000000000000000000000000000..089625ca1b340ff5718be52bdb31cc4b43c579ea
--- /dev/null
+++ b/scp/image.php
@@ -0,0 +1,31 @@
+<?php
+/*********************************************************************
+    image.php
+
+    Simply downloads the file...on hash validation as follows;
+
+    * Hash must be 64 chars long.
+    * First 32 chars is the perm. file hash
+    * Next 32 chars  is md5(file_id.session_id().file_hash)
+
+    Peter Rotich <peter@osticket.com>
+    Copyright (c)  2006-2013 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+
+require('staff.inc.php');
+require_once(INCLUDE_DIR.'class.file.php');
+$h=trim($_GET['h']);
+//basic checks
+if(!$h  || strlen($h)!=64  //32*2
+        || !($file=AttachmentFile::lookup(substr($h,0,32))) //first 32 is the file hash.
+        || strcasecmp($h, $file->getDownloadHash())) //next 32 is file id + session hash.
+    die('Unknown or invalid file. #'.Format::htmlchars($_GET['h']));
+
+$file->display();
+?>
diff --git a/secure.inc.php b/secure.inc.php
index 7b94ff92f2b10618de9a2921773c098f475ee4d8..bf6a75b3e032590ca6ec89346e50c67393fd5514 100644
--- a/secure.inc.php
+++ b/secure.inc.php
@@ -20,6 +20,7 @@ require_once('client.inc.php');
 //Client Login page: Ajax interface can pre-declare the function to trap logins.
 if(!function_exists('clientLoginPage')) {
     function clientLoginPage($msg ='') {
+        global $ost;
         require('./login.php');
         exit;
     }