diff --git a/api/api.inc.php b/api/api.inc.php
index 48836382022d6b8ddec422e0afc46ee074930567..5167dc496b4231264777fd93dd71886eb55a7875 100644
--- a/api/api.inc.php
+++ b/api/api.inc.php
@@ -2,7 +2,7 @@
 /*********************************************************************
     api.inc.php
 
-    File included on every API page...handles security and abuse issues
+    File included on every API page...handles common includes.
 
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2012 osTicket
@@ -13,74 +13,9 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-//postfix exit codes see /usr/include/sysexits.h 
-define('EX_DATAERR', 65);       /* data format error */
-define('EX_NOINPUT', 66);       /* cannot open input */
-define('EX_UNAVAILABLE', 69);   /* service unavailable */
-define('EX_IOERR', 74);         /* input/output error */
-define('EX_TEMPFAIL',75);       /* temp failure; user is invited to retry */
-define('EX_NOPERM',  77);       /* permission denied */
-define('EX_CONFIG',  78);       /* configuration error */
-
-define('EX_SUCCESS',0);         /* success baby */
-
-if(!file_exists('../main.inc.php')) exit(EX_CONFIG);
+file_exists('../main.inc.php') or die('System Error');
 require_once('../main.inc.php');
-if(!defined('INCLUDE_DIR')) exit(EX_CONFIG);
-
 require_once(INCLUDE_DIR.'class.http.php');
 require_once(INCLUDE_DIR.'class.api.php');
 
-define('OSTAPIINC',TRUE); // Define tag that included files can check
-
-$remotehost=(isset($_SERVER['HTTP_HOST']) || isset($_SERVER['REMOTE_ADDR']))?TRUE:FALSE;
-/* API exit helper */
-function api_exit($code,$msg='') {
-    global $remotehost, $ost;
-    
-    if($code!=EX_SUCCESS) {
-        //Error occured...
-        $_SESSION['api']['errors']+=1;
-        $_SESSION['api']['time']=time();
-        $ost->logWarning("API error - code #$code", $msg, ($_SESSION['api']['errors']>10));
-        //echo "API Error:.$msg";
-    }
-    if($remotehost){
-        switch($code) {
-        case EX_SUCCESS:
-            Http::response(200,$code,'text/plain');
-            break;
-        case EX_UNAVAILABLE:
-            Http::response(405,$code,'text/plain');
-            break;
-        case EX_NOPERM:
-            Http::response(403,$code,'text/plain');
-            break;
-        case EX_DATAERR:
-        case EX_NOINPUT:
-        default:
-            Http::response(416,$code,'text/plain');
-        }
-    }
-    exit($code);
-}
-
-//Remote hosts need authorization.
-$apikey = null;
-if($remotehost) {
-    //Upto 10 consecutive errors allowed...before a 2 minute timeout.
-    //One more error during timeout and timeout starts a new clock
-    if($_SESSION['api']['errors']>10 && (time()-$_SESSION['api']['time'])<=2*60)  // timeout!
-        api_exit(EX_NOPERM, 'Remote host ['.$_SERVER['REMOTE_ADDR'].'] in timeout - error #'.$_SESSION['api']['errors']);
-        
-    if(!isset($_SERVER['HTTP_X_API_KEY']) || !isset($_SERVER['REMOTE_ADDR']))
-        api_exit(EX_NOPERM, 'API key required');
-    elseif(!($apikey=API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR']))
-                || !$apikey->isActive()
-                || $apikey->getIPAddr()!=$_SERVER['REMOTE_ADDR'])
-        api_exit(EX_NOPERM, 'API key not found/active or source IP not authorized');
-    
-    //At this point we know the remote host/IP is allowed.
-    $_SESSION['api']['errors']=0; //clear errors for the session.
-}
 ?>
diff --git a/api/cron.php b/api/cron.php
index a8874e3b7eb45eb246145e756db8f85c91c1e47a..2e605713e733e20ffb95bd9da397df990572f550 100644
--- a/api/cron.php
+++ b/api/cron.php
@@ -2,7 +2,7 @@
 /*********************************************************************
     cron.php
 
-    File to handle cron job calls (local and remote).
+    File to handle LOCAL cron job calls.
 
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2012 osTicket
@@ -13,9 +13,11 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
+if (substr(php_sapi_name(), 0, 3) != 'cli')
+    die('cron.php only supports local cron jobs - use http -> api/task/cron');
+
 @chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
 require('api.inc.php');
-require_once(INCLUDE_DIR.'class.cron.php');
-Cron::run();
-$ost->logDebug('Cron Job','External cron job executed ['.$_SERVER['REMOTE_ADDR'].']');
+require_once(INCLUDE_DIR.'api.cron.php');
+LocalCronApiController::call();
 ?>
diff --git a/api/http.php b/api/http.php
index 646df6798fb8a6f0cdfcc4425114f6dfff71ee91..14eeecd4f85c008df255cf23a5282d57c30d010d 100644
--- a/api/http.php
+++ b/api/http.php
@@ -1,8 +1,8 @@
 <?php
 /*********************************************************************
-    api.php
+    http.php
 
-    Controller for the osTicket API
+    HTTP controller for the osTicket API
 
     Jared Hancock
     Copyright (c)  2006-2012 osTicket
@@ -13,15 +13,18 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-
-chdir('..');
-include "main.inc.php";
+require 'api.inc.php';
 
 # Include the main api urls
 require_once INCLUDE_DIR."class.dispatcher.php";
-$dispatcher = Dispatcher::include_urls("urls.conf.php");
 
-# Call the respective function
-$dispatcher->resolve($_SERVER['PATH_INFO']);
+$dispatcher = patterns('',
+        url_post("^/tickets\.(?P<format>xml|json|email)$", array('api.tickets.php:TicketApiController','create')),
+        url('^/task/', patterns('',
+                url_post("^cron$", array('api.cron.php:CronApiController', 'execute'))
+         ))
+        );
 
+# Call the respective function
+print $dispatcher->resolve($_SERVER['PATH_INFO']);
 ?>
diff --git a/api/pipe.php b/api/pipe.php
index ff23cfa1b56c0f6fa75824f8106337c7440f6cb9..7429a6367a351882fef425d5d42f04f6b0919f34 100644
--- a/api/pipe.php
+++ b/api/pipe.php
@@ -3,7 +3,7 @@
 /*********************************************************************
     pipe.php
 
-    Converts piped emails to ticket. Both local and remote!
+    Converts piped emails to ticket. Just local - remote must use /api/tickets.email
 
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2012 osTicket
@@ -14,122 +14,14 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-@chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
-ini_set('memory_limit', '256M'); //The concern here is having enough mem for emails with attachments.
-$apikey = null;
-require('api.inc.php');
-require_once(INCLUDE_DIR.'class.mailparse.php');
-require_once(INCLUDE_DIR.'class.email.php');
-
-//Make sure piping is enabled!
-if(!$cfg->isEmailPipingEnabled())
-    api_exit(EX_UNAVAILABLE,'Email piping not enabled - check MTA settings.');
-elseif($apikey && !$apikey->canCreateTickets()) //apikey is ONLY set on remote post - local post don't need a key (for now).
-    api_exit(EX_NOPERM, 'API key not authorized');
-
-//Get the input
-$data=isset($_SERVER['HTTP_HOST'])?file_get_contents('php://input'):file_get_contents('php://stdin');
-if(empty($data)){
-    api_exit(EX_NOINPUT,'No data');
-}
-
-//Parse the email.
-$parser= new Mail_Parse($data);
-if(!$parser->decode()){ //Decode...returns false on decoding errors
-    api_exit(EX_DATAERR,'Email parse failed ['.$parser->getError()."]\n\n".$data);    
-}
-
-
-
-//Check from address. make sure it is not a banned address.
-$fromlist = $parser->getFromAddressList();
-//Check for parsing errors on FROM address.
-if(!$fromlist || PEAR::isError($fromlist)){
-    api_exit(EX_DATAERR,'Invalid FROM address ['.$fromlist?$fromlist->getMessage():''."]\n\n".$data);
-}
-
-$from=$fromlist[0]; //Default.
-foreach($fromlist as $fromobj){
-    if(!Validator::is_email($fromobj->mailbox.'@'.$fromobj->host))
-        continue;
-    $from=$fromobj;
-    break;
-}
-
-//TO Address:Try to figure out the email associated with the message.
-$tolist = $parser->getToAddressList();
-foreach ($tolist as $toaddr){
-    if(($emailId=Email::getIdByEmail($toaddr->mailbox.'@'.$toaddr->host))){
-        //We've found target email.
-        break;
-    }
-}
-if(!$emailId && ($cclist=$parser->getCcAddressList())) {
-    foreach ($cclist as $ccaddr){
-        if(($emailId=Email::getIdByEmail($ccaddr->mailbox.'@'.$ccaddr->host))){
-            break;
-        }
-    }
-}
-//TODO: Options to reject emails without a matching To address in db? May be it was Bcc? Current Policy: If you pipe, we accept policy
 
-require_once(INCLUDE_DIR.'class.ticket.php'); //We now need this bad boy!
+//Only local piping supported via pipe.php
+if (substr(php_sapi_name(), 0, 3) != 'cli')
+    die('pipe.php only supports local piping - use http -> api/tickets.email');
 
-$var=array();
-$deptId=0;
-$name=trim($from->personal,'"');
-if($from->comment && $from->comment[0])
-    $name.=' ('.$from->comment[0].')';
-$subj=utf8_encode($parser->getSubject());
-if(!($body=Format::stripEmptyLines($parser->getBody())))
-    $body=$subj?$subj:'(EMPTY)';
-
-$var['mid']=$parser->getMessageId();
-$var['email']=$from->mailbox.'@'.$from->host;
-$var['name']=$name?utf8_encode($name):$var['email'];
-$var['emailId']=$emailId?$emailId:$cfg->getDefaultEmailId();
-$var['subject']=$subj?$subj:'[No Subject]';
-$var['message']=utf8_encode(Format::stripEmptyLines($body));
-$var['header']=$parser->getHeader();
-$var['priorityId']=$cfg->useEmailPriority()?$parser->getPriority():0;
-
-$ticket=null;
-if(preg_match ("[[#][0-9]{1,10}]", $var['subject'], $regs)) {
-    $extid=trim(preg_replace("/[^0-9]/", "", $regs[0]));
-    if(!($ticket=Ticket::lookupByExtId($extid, $var['email'])) || strcasecmp($ticket->getEmail(), $var['email']))
-       $ticket = null;
-}        
-
-$errors=array();
-$msgid=0;
-if($ticket) {
-    //post message....postMessage does the cleanup.
-    if(!($msgid=$ticket->postMessage($var['message'], 'Email',$var['mid'],$var['header'])))
-        api_exit(EX_DATAERR, 'Unable to post message');
-
-} elseif(($ticket=Ticket::create($var, $errors, 'email'))) { // create new ticket.
-    $msgid=$ticket->getLastMsgId();
-} else { // failure....
-
-    // report success on hard rejection
-    if(isset($errors['errno']) && $errors['errno'] == 403)
-        api_exit(EX_SUCCESS);
-
-    // check if it's a bounce!
-    if($var['header'] && TicketFilter::isAutoBounce($var['header'])) {
-        $ost->logWarning('Bounced email', $var['message'], false);
-        api_exit(EX_SUCCESS); 
-    }
-    
-    api_exit(EX_DATAERR, 'Ticket create Failed '.implode("\n",$errors)."\n\n");
-}
-
-//Ticket created...save attachments if enabled.
-if($ticket && $cfg->allowEmailAttachments() && ($attachments=$parser->getAttachments())) {
-    foreach($attachments as $attachment) {
-        if($attachment['filename'] && $ost->isFileTypeAllowed($attachment['filename']))
-            $ticket->saveAttachment(array('name' => $attachment['filename'], 'data' => $attachment['body']), $msgid, 'M');
-    }
-}
-api_exit(EX_SUCCESS);
+ini_set('memory_limit', '256M'); //The concern here is having enough mem for emails with attachments.
+@chdir(realpath(dirname(__FILE__)).'/'); //Change dir.
+require('api.inc.php');
+require_once(INCLUDE_DIR.'api.tickets.php');
+PipeApiController::process();
 ?>
diff --git a/api/urls.conf.php b/api/urls.conf.php
deleted file mode 100644
index b6a9555e72565e4d945b78ab30f17be0c63bfd84..0000000000000000000000000000000000000000
--- a/api/urls.conf.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-# What about patterns("api/ticket.php:TicketController", ...) since if the
-# class is given as the prefix, it isn't defined in more than one file. This
-# would allow for speficying imports only if an item is defined in a
-# different class (with the array("class", "method") syntax)
-return patterns("api.ticket.php:TicketController",
-    url_post("^/tickets\.(?P<format>xml|json)$", "create")
-);
-
-?>
diff --git a/include/api.cron.php b/include/api.cron.php
new file mode 100644
index 0000000000000000000000000000000000000000..912fff41090df4887598a3b7f6cd0f915910b73b
--- /dev/null
+++ b/include/api.cron.php
@@ -0,0 +1,43 @@
+<?php
+
+include_once INCLUDE_DIR.'class.cron.php';
+
+class CronApiController extends ApiController {
+
+    function execute() {
+
+        if(!($key=$this->requireApiKey()) || !$key->canExecuteCronJob())
+            return $this->exerr(401, 'API key not authorized');
+
+        $this->run();
+    }
+
+    /* private */
+    function run() {
+        global $ost;
+
+        Cron::run();
+       
+        $ost->logDebug('Cron Job','Cron job executed ['.$_SERVER['REMOTE_ADDR'].']');
+        $this->response(200,'Completed');
+    }
+}
+
+class LocalCronApiController extends CronApiController {
+
+    function response($code, $resp) {
+
+        if($code == 200) //Success - exit silently.
+            exit(0);
+        
+        //On error echo the response (error)
+        echo $resp;
+        exit(1);
+    }
+        
+    function call() {
+        $cron = new LocalCronApiController();
+        $cron->run();
+    }
+}
+?>
diff --git a/include/api.ticket.php b/include/api.ticket.php
deleted file mode 100644
index 7fd5ba713c8237aad483c4a44b08989cf9b17c3f..0000000000000000000000000000000000000000
--- a/include/api.ticket.php
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-
-include_once "include/class.api.php";
-include_once "include/class.ticket.php";
-
-class TicketController extends ApiController {
-
-    # Supported arguments -- anything else is an error. These items will be
-    # inspected _after_ the fixup() method of the ApiXxxDataParser classes
-    # so that all supported input formats should be supported
-    function getRequestStructure($format) {
-        $supported = array(
-            "alert", "autorespond", "source", "topicId",
-            "name", "email", "subject", "phone", "phone_ext",
-            "attachments" => array("*" => 
-                array("name", "type", "data", "encoding")
-            ), 
-            "message", "ip", "priorityId"
-        );
-        if ($format == "xml") return array("ticket" => $supported);
-        else return $supported;
-    }
-
-    function create($format) {
-
-        if(!($key=$this->getApiKey()) || !$key->canCreateTickets())
-            Http::response(401, 'API key not authorized');
-
-        # Parse request body
-        $data = $this->getRequest($format);
-        if ($format == "xml") $data = $data["ticket"];
-
-        # Pull off some meta-data
-        $alert = $data['alert'] ? $data['alert'] : true; 
-        $autorespond = $data['autorespond'] ? $data['autorespond'] : true;
-        $source = $data['source'] ? $data['source'] : 'API';
-
-        $attachments = $data['attachments'] ? $data['attachments'] : array();
-
-		# TODO: Handle attachment encoding (base64)
-        foreach ($attachments as $filename=>&$info) {
-            if ($info["encoding"] == "base64") {
-                # XXX: May fail on large inputs. See
-                #      http://us.php.net/manual/en/function.base64-decode.php#105512
-                if (!($info["data"] = base64_decode($info["data"], true)))
-                    Http::response(400, sprintf(
-                        "%s: Poorly encoded base64 data",
-                        $info['name']));
-            }
-            $info['size'] = strlen($info['data']);
-        }
-
-        # Create the ticket with the data (attempt to anyway)
-        $errors = array();
-        $ticket = Ticket::create($data, $errors, $source, $autorespond, 
-            $alert);
-
-        # Return errors (?)
-        if (count($errors)) {
-            Http::response(400, "Unable to create new ticket: validation errors:\n"
-                . Format::array_implode(": ", "\n", $errors));
-        } elseif (!$ticket) {
-            Http::response(500, "Unable to create new ticket: unknown error");
-        }
-
-        # Save attachment(s)
-        foreach ($attachments as &$info)
-            $ticket->saveAttachment($info, $ticket->getLastMsgId(), "M");
-
-        # All done. Return HTTP/201 --> Created
-        Http::response(201, $ticket->getExtId());
-    }
-}
-
-?>
diff --git a/include/api.tickets.php b/include/api.tickets.php
new file mode 100644
index 0000000000000000000000000000000000000000..7346f2b2943bbc5c0451d62fcc7952677034b6d5
--- /dev/null
+++ b/include/api.tickets.php
@@ -0,0 +1,140 @@
+<?php
+
+include_once INCLUDE_DIR.'class.api.php';
+include_once INCLUDE_DIR.'class.ticket.php';
+
+class TicketApiController extends ApiController {
+
+    # Supported arguments -- anything else is an error. These items will be
+    # inspected _after_ the fixup() method of the ApiXxxDataParser classes
+    # so that all supported input formats should be supported
+    function getRequestStructure($format) {
+        $supported = array(
+            "alert", "autorespond", "source", "topicId",
+            "name", "email", "subject", "phone", "phone_ext",
+            "attachments" => array("*" => 
+                array("name", "type", "data", "encoding")
+            ), 
+            "message", "ip", "priorityId"
+        );
+
+        if(!strcasecmp($format, 'email'))
+            $supported = array_merge($supported, array('header', 'mid', 'emailId', 'ticketId'));
+
+        return $supported;
+    }
+
+    function create($format) {
+
+        if(!($key=$this->requireApiKey()) || !$key->canCreateTickets())
+            return $this->exerr(401, 'API key not authorized');
+
+        $ticket = null;
+        if(!strcasecmp($format, 'email')) {
+            # Handle remote piped emails - could be a reply...etc.
+            $ticket = $this->processEmail();
+        } else {
+            # Parse request body
+            $ticket = $this->createTicket($this->getRequest($format));
+        }
+
+        if(!$ticket)
+            return $this->exerr(500, "Unable to create new ticket: unknown error");
+
+        $this->response(201, $ticket->getExtId());
+    }
+
+    /* private helper functions */
+
+    function createTicket($data) {
+
+        # Pull off some meta-data
+        $alert = $data['alert'] ? $data['alert'] : true;
+        $autorespond = $data['autorespond'] ? $data['autorespond'] : true;
+        $data['source'] = $data['source'] ? $data['source'] : 'API';
+
+        # Create the ticket with the data (attempt to anyway)
+        $errors = array();
+        $ticket = Ticket::create($data, $errors, $data['source'], $autorespond, $alert);
+        # Return errors (?)
+        if (count($errors)) {
+            if(isset($errors['errno']) && $errors['errno'] == 403)
+                return $this->exerr(403, 'Ticket denied');
+            else
+                return $this->exerr(
+                        400, 
+                        "Unable to create new ticket: validation errors:\n"
+                        .Format::array_implode(": ", "\n", $errors)
+                        );
+        } elseif (!$ticket) {
+            return $this->exerr(500, "Unable to create new ticket: unknown error");
+        }
+
+        
+        # Save attachment(s)
+        if($data['attachments'])
+            $ticket->importAttachments($data['attachments'], $ticket->getLastMsgId(), 'M');
+
+        return $ticket;
+    }
+
+    function processEmail() {
+
+        $data = $this->getEmailRequest();
+        if($data['ticketId'] && ($ticket=Ticket::lookup($data['ticketId']))) {
+            if(($msgid=$ticket->postMessage($data, 'Email')))
+                return $ticket;
+        }
+
+        return $this->createTicket($data);
+    }
+
+}
+
+//Local email piping controller - no API key required!
+class PipeApiController extends TicketApiController {
+
+    //Overwrite grandparent's (ApiController) response method.
+    function response($code, $resp) {
+
+        //Use postfix exit codes - instead of HTTP 
+        switch($code) {
+            case 201: //Success
+                $exitcode = 0;
+                break;
+            case 400:
+                $exitcode = 66;
+                break;
+            case 401: /* permission denied */
+            case 403:
+                $exitcode = 77;
+                break;
+            case 415:
+            case 416:
+            case 417:
+            case 501:
+                $exitcode = 65;
+                break;
+            case 503:
+                $exitcode = 69;
+                break;
+            case 500: //Server error.
+            default: //Temp (unknown) failure - retry 
+                $exitcode = 75; 
+        }
+
+        //echo "$code ($exitcode):$resp";
+        //We're simply exiting - MTA will take care of the rest based on exit code!
+        exit($exitcode);
+    }
+
+    function  process() {
+        $pipe = new PipeApiController();
+        if(($ticket=$pipe->processEmail()))
+           return $pipe->response(201, $ticket->getNumber());
+
+        return $pipe->exerr(416, 'Request failed -retry again!');
+    }
+}
+
+?>
diff --git a/include/class.api.php b/include/class.api.php
index df8cfb0c8a9cb9f452b62de56c1e15181731d614..f5d2ef5c7bbf6daef57b87903108f546961fbab6 100644
--- a/include/class.api.php
+++ b/include/class.api.php
@@ -71,6 +71,10 @@ class API {
         return ($this->ht['can_create_tickets']);
     }
 
+    function canExecuteCronjob() {
+        return ($this->ht['can_exec_cron']);
+    }
+
     function update($vars, &$errors) {
 
         if(!API::save($this->getId(), $vars, $errors))
@@ -125,6 +129,7 @@ class API {
         $sql=' updated=NOW() '
             .',isactive='.db_input($vars['isactive'])
             .',can_create_tickets='.db_input($vars['can_create_tickets'])
+            .',can_exec_cron='.db_input($vars['can_exec_cron'])
             .',notes='.db_input($vars['notes']);
 
         if($id) {
@@ -156,23 +161,29 @@ class API {
  * in the database, and methods for parsing and validating data sent in the
  * API request.
  */
+
 class ApiController {
 
+    var $apikey;
+
     function requireApiKey() {
         # Validate the API key -- required to be sent via the X-API-Key
         # header
-        if (!isset($_SERVER['HTTP_X_API_KEY']) || !isset($_SERVER['REMOTE_ADDR']))
-            Http::response(403, "API key required");
-        elseif (!($key=API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR']))
-                || !$key->isActive() 
-                || $key->getIPAddr()!=$_SERVER['REMOTE_ADDR'])
-            Http::response(401, "API key not found/active or source IP not authorized");
+
+        if(!($key=$this->getApiKey()))
+            return $this->exerr(401, 'Valid API key required');
+        elseif (!$key->isActive() || $key->getIPAddr()!=$_SERVER['REMOTE_ADDR'])
+            return $this->exerr(401, 'API key not found/active or source IP not authorized');
 
         return $key;
     }
 
     function getApiKey() {
-        return $this->requireApiKey();
+
+        if (!$this->apikey && isset($_SERVER['HTTP_X_API_KEY']) && isset($_SERVER['REMOTE_ADDR']))
+            $this->apikey = API::lookupByKey($_SERVER['HTTP_X_API_KEY'], $_SERVER['REMOTE_ADDR']);
+
+        return $this->apikey;
     }
 
     /**
@@ -181,20 +192,43 @@ class ApiController {
      * work will be done for XML requests
      */
     function getRequest($format) {
-        if (!($stream = @fopen("php://input", "r")))
-            Http::response(400, "Unable to read request body");
-        if ($format == "xml") {
-            if (!function_exists("xml_parser_create"))
-                Http::response(500, "XML extension not supported");
-            $tree = new ApiXmlDataParser();
-        } elseif ($format == "json") {
-            $tree = new ApiJsonDataParser();
+        
+        $input = (substr(php_sapi_name(), 0, 3) == 'cli')?'php://stdin':'php://input';
+
+        if (!($stream = @fopen($input, 'r')))
+            $this->exerr(400, "Unable to read request body");
+
+        $parser = null;
+        switch(strtolower($format)) {
+            case 'xml':
+                if (!function_exists('xml_parser_create'))
+                    $this->exerr(501, 'XML extension not supported');
+
+                $parser = new ApiXmlDataParser();
+                break;
+            case 'json':
+                $parser = new ApiJsonDataParser();
+                break;
+            case 'email':
+                $parser = new ApiEmailDataParser();
+                break;
+            default:
+                $this->exerr(415, 'Unsupported data format');
         }
-        if (!($data = $tree->parse($stream)))
-            Http::response(400, $tree->lastError());
+
+        if (!($data = $parser->parse($stream)))
+            $this->exerr(400, $parser->lastError());
+       
         $this->validate($data, $this->getRequestStructure($format));
+
         return $data;
     }
+
+    function getEmailRequest() {
+        return $this->getRequest('email');
+    }
+
+
     /**
      * Structure to validate the request against -- must be overridden to be
      * useful
@@ -216,9 +250,37 @@ class ApiController {
             } elseif (in_array($key, $structure)) {
                 continue;
             }
-            Http::response(400, "$prefix$key: Unexpected data received");
+            $this->exerr(400, "$prefix$key: Unexpected data received");
         }
     }
+
+    /**
+     * API error & logging and response!
+     *
+     */
+
+    /* If possible - DO NOT - overwrite the method downstream */
+    function exerr($code, $error='') {
+        global $ost;
+
+        if($error && is_array($error))
+            $error = Format::array_implode(": ", "\n", $error);
+
+        //Log the error as a warning - include api key if available.
+        $msg = $error;
+        if($_SERVER['HTTP_X_API_KEY'])
+            $msg.="\n*[".$_SERVER['HTTP_X_API_KEY']."]*\n";
+        $ost->logWarning("API Error ($code)", $msg, false);
+
+        $this->response($code, $error); //Responder should exit...
+        return false;
+    }
+
+    //Default response method - can be overwritten in subclasses.
+    function response($code, $resp) {
+        Http::response($code, $resp);
+        exit();
+    }
 }
 
 include_once "class.xml.php";
@@ -232,6 +294,10 @@ class ApiXmlDataParser extends XmlDataParser {
      * XML data types
      */
     function fixup($current) {
+
+        if($current['ticket'])
+            $current = $current['ticket'];
+
         if (!is_array($current))
             return $current;
         foreach ($current as $key=>&$value) {
@@ -295,6 +361,7 @@ class ApiJsonDataParser extends JsonDataParser {
                             "data" => $contents,
                             "type" => ($type) ? $type : "text/plain",
                             "name" => key($info));
+                        # XXX: Handle decoding here??
                         if (substr($extra, -6) == "base64")
                             $info["encoding"] = "base64";
                         # Handle 'charset' hint in $extra, such as
@@ -302,8 +369,8 @@ class ApiJsonDataParser extends JsonDataParser {
                         # Convert to utf-8 since it's the encoding scheme
                         # for the database. Otherwise, assume utf-8
                         list($param,$charset) = explode('=', $extra);
-                        if ($param == 'charset' && function_exists('iconv'))
-                            $contents = iconv($charset, "UTF-8", $contents);
+                        if ($param == 'charset' && $charset)
+                            $contents = Formart::utf8encode($contents, $charset);
                     }
                 }
                 unset($value);
@@ -316,4 +383,39 @@ class ApiJsonDataParser extends JsonDataParser {
     }
 }
 
+/* Email parsing */
+include_once "class.mailparse.php";
+class ApiEmailDataParser extends EmailDataParser {
+
+    function parse($stream) {
+        return $this->fixup(parent::parse($stream));
+    }
+
+    function fixup($data) {
+        global $cfg;
+
+        if(!$data) return $data;
+
+        $data['source'] = 'Email';
+
+        if(!$data['message'])
+            $data['message'] = $data['subject']?$data['subject']:'(EMPTY)';
+
+        if(!$data['subject'])
+            $data['subject'] = '[No Subject]';
+
+        if(!$data['emailId'])
+            $data['emailId'] = $cfg->getDefaultEmailId();
+
+        if($data['email'] && preg_match ('[[#][0-9]{1,10}]', $data['subject'], $matches)) {
+            if(($tid=Ticket::getIdByExtId(trim(preg_replace('/[^0-9]/', '', $matches[0])), $data['email'])))
+                $data['ticketId'] = $tid;
+        }
+
+        if(!$cfg->useEmailPriority())
+            unset($data['priorityId']);
+
+        return $data;
+    }
+}
 ?>
diff --git a/include/class.config.php b/include/class.config.php
index c7e9669742d26f9e45063e02d26d1b2a7737e16a..50a3174c62cafaa8d3888674051b8f1ba7e49444 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -336,10 +336,6 @@ class Config {
         return ($this->config['enable_mail_polling']);
     }
         
-    function isEmailPipingEnabled() {
-        return ($this->config['enable_email_piping']);
-    }
-
     function allowPriorityChange() {
         return ($this->config['allow_priority_change']);
     }
@@ -723,7 +719,6 @@ class Config {
             .',admin_email='.db_input($vars['admin_email'])
             .',enable_auto_cron='.db_input(isset($vars['enable_auto_cron'])?1:0)
             .',enable_mail_polling='.db_input(isset($vars['enable_mail_polling'])?1:0)
-            .',enable_email_piping='.db_input(isset($vars['enable_email_piping'])?1:0)
             .',strip_quoted_reply='.db_input(isset($vars['strip_quoted_reply'])?1:0)
             .',reply_separator='.db_input($vars['reply_separator'])
             .' WHERE id='.db_input($this->getId());
diff --git a/include/class.format.php b/include/class.format.php
index af3fb0c31d2a65674631d85a2b90e86d988b5b3e..38bc1ac14690c721cb3f2a253a6340dbaf025ae2 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -46,6 +46,31 @@ class Format {
         return $result?array_filter($result):$files;
     }
 
+
+    /* encode text into desired encoding - taking into accout charset when available. */
+    function encode($text, $charset=null, $encoding='utf-8') {
+
+        //Try auto-detecting charset/encoding
+        if(!$charset && function_exists('mb_detect_encoding')) 
+            $charset = mb_detect_encoding($text);
+
+        //Cleanup - junk 
+        if($charset && in_array(trim($charset), array('default','x-user-defined')))
+            $charset = 'ISO-8859-1'; 
+
+        if(function_exists('iconv') && $charset)
+            return iconv($charset, $encoding.'//IGNORE', $text);
+        elseif(function_exists('iconv_mime_decode'))
+            return iconv_mime_decode($text, 0, $encoding);
+        else //default to utf8 encoding.
+            return utf8_encode($text);
+    }
+
+    //Wrapper for utf-8 encoding.
+    function utf8encode($text, $charset=null) {
+        return Format::enecode($text, $charset, 'utf-8');
+    }
+
 	function phone($phone) {
 
 		$stripped= preg_replace("/[^0-9]/", "", $phone);
diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php
index 1be504b89e17e1ebf6ed62b9438aeda9a92604ae..d0b663f66b8a72c28ad27e12aa4013f3884a1eab 100644
--- a/include/class.mailfetch.php
+++ b/include/class.mailfetch.php
@@ -171,7 +171,7 @@ class MailFetcher {
     }
 
 
-    function decode($encoding, $text) {
+    function decode($text, $encoding) {
 
         switch($encoding) {
             case 1:
@@ -186,38 +186,23 @@ class MailFetcher {
             case 4:
             $text=imap_qprint($text);
             break;
-            case 5:
-            default:
-             $text=$text;
-        } 
+        }
+        
         return $text;
     }
 
     //Convert text to desired encoding..defaults to utf8
-    function mime_encode($text, $charset=null, $enc='utf-8') { //Thank in part to afterburner 
-
-        if($charset && in_array(trim($charset), array('default','x-user-defined')))
-            $charset = 'ASCII';
-        
-        if(function_exists('iconv') and ($charset or function_exists('mb_detect_encoding'))) {
-            if($charset)
-                return iconv($charset, $enc.'//IGNORE', $text);
-            elseif(function_exists('mb_detect_encoding'))
-                return iconv(mb_detect_encoding($text, $this->encodings), $enc, $text);
-        } elseif(function_exists('iconv_mime_decode')) {
-            return iconv_mime_decode($text, 0, $enc);
-        }
-
-        return utf8_encode($text);
+    function mime_encode($text, $charset=null, $encoding='utf-8') { //Thank in part to afterburner 
+        return Format::encode($text, $charset, $encoding);
     }
     
     //Generic decoder - resulting text is utf8 encoded -> mirrors imap_utf8
-    function mime_decode($text, $enc='utf-8') {
+    function mime_decode($text, $encoding='utf-8') {
         
         $str = '';
         $parts = imap_mime_header_decode($text);
         foreach ($parts as $part)
-            $str.= $this->mime_encode($part->text, $part->charset, $enc);
+            $str.= $this->mime_encode($part->text, $part->charset, $encoding);
         
         return $str?$str:imap_utf8($text);
     }
@@ -261,7 +246,7 @@ class MailFetcher {
             $partNumber=$partNumber?$partNumber:1;
             if(($text=imap_fetchbody($this->mbox, $mid, $partNumber))) {
                 if($struct->encoding==3 or $struct->encoding==4) //base64 and qp decode.
-                    $text=$this->decode($struct->encoding, $text);
+                    $text=$this->decode($text, $struct->encoding);
 
                 $charset=null;
                 if($encoding) { //Convert text to desired mime encoding...
@@ -323,7 +308,7 @@ class MailFetcher {
                 return array(
                         array(
                             'name'  => $this->mime_decode($filename),
-                            'mime'  => $this->getMimeType($part),
+                            'type'  => $this->getMimeType($part),
                             'encoding' => $part->encoding,
                             'index' => ($index?$index:1)
                             )
@@ -387,38 +372,38 @@ class MailFetcher {
         }
 
         $emailId = $this->getEmailId();
-
-        $var['name']=$this->mime_decode($mailinfo['name']);
-        $var['email']=$mailinfo['email'];
-        $var['subject']=$mailinfo['subject']?$this->mime_decode($mailinfo['subject']):'[No Subject]';
-        $var['message']=Format::stripEmptyLines($this->getBody($mid));
-        $var['header']=$this->getHeader($mid);
-        $var['emailId']=$emailId?$emailId:$ost->getConfig()->getDefaultEmailId(); //ok to default?
-        $var['name']=$var['name']?$var['name']:$var['email']; //No name? use email
-        $var['mid']=$mailinfo['mid'];
-
-        if(!$var['message']) //An email with just attachments can have empty body.
-            $var['message'] = '(EMPTY)';
+        $vars = array();
+        $vars['name']=$this->mime_decode($mailinfo['name']);
+        $vars['email']=$mailinfo['email'];
+        $vars['subject']=$mailinfo['subject']?$this->mime_decode($mailinfo['subject']):'[No Subject]';
+        $vars['message']=Format::stripEmptyLines($this->getBody($mid));
+        $vars['header']=$this->getHeader($mid);
+        $vars['emailId']=$emailId?$emailId:$ost->getConfig()->getDefaultEmailId(); //ok to default?
+        $vars['name']=$vars['name']?$vars['name']:$vars['email']; //No name? use email
+        $vars['mid']=$mailinfo['mid'];
+
+        if(!$vars['message']) //An email with just attachments can have empty body.
+            $vars['message'] = '(EMPTY)';
 
         if($ost->getConfig()->useEmailPriority())
-            $var['priorityId']=$this->getPriority($mid);
+            $vars['priorityId']=$this->getPriority($mid);
        
         $ticket=null;
         $newticket=true;
         //Check the subject line for possible ID.
-        if($var['subject'] && preg_match ("[[#][0-9]{1,10}]", $var['subject'], $regs)) {
+        if($vars['subject'] && preg_match ("[[#][0-9]{1,10}]", $vars['subject'], $regs)) {
             $tid=trim(preg_replace("/[^0-9]/", "", $regs[0]));
             //Allow mismatched emails?? For now NO.
-            if(!($ticket=Ticket::lookupByExtId($tid)) || strcasecmp($ticket->getEmail(), $var['email']))
+            if(!($ticket=Ticket::lookupByExtId($tid, $vars['email'])))
                 $ticket=null;
         }
         
         $errors=array();
         if($ticket) {
-            if(!($msgid=$ticket->postMessage($var['message'], 'Email', $var['mid'], $var['header'])))
+            if(!($msgid=$ticket->postMessage($vars, 'Email')))
                 return false;
 
-        } elseif (($ticket=Ticket::create($var, $errors, 'Email'))) {
+        } elseif (($ticket=Ticket::create($vars, $errors, 'Email'))) {
             $msgid = $ticket->getLastMsgId();
         } else {
             //Report success if the email was absolutely rejected.
@@ -426,8 +411,8 @@ class MailFetcher {
                 return true;
 
             # check if it's a bounce!
-            if($var['header'] && TicketFilter::isAutoBounce($var['header'])) {
-                $ost->logWarning('Bounced email', $var['message'], false);
+            if($vars['header'] && TicketFilter::isAutoBounce($vars['header'])) {
+                $ost->logWarning('Bounced email', $vars['message'], false);
                 return true;
             }
 
@@ -442,23 +427,13 @@ class MailFetcher {
                 && $struct->parts 
                 && ($attachments=$this->getAttachments($struct))) {
                 
-            //We're just checking the type of file - not size or number of attachments...
-            // Restrictions are mainly due to PHP file uploads limitations
             foreach($attachments as $a ) {
-                if($ost->isFileTypeAllowed($a['name'], $a['mime'])) {
-                    $file = array(
-                            'name'  => $a['name'],
-                            'type'  => $a['mime'],
-                            'data'  => $this->decode($a['encoding'], imap_fetchbody($this->mbox, $mid, $a['index']))
-                            );
-                    $ticket->saveAttachment($file, $msgid, 'M');
-                } else {
-                    //This should be really a comment on message - NoT an internal note.
-                    //TODO: support comments on Messages and Responses.
-                    $error = sprintf('Attachment %s [%s] rejected because of file type', $a['name'], $a['mime']);
-                    $ticket->postNote('Email Attachment Rejected', $error, 'SYSTEM', false);
-                    $ost->logDebug('Email Attachment Rejected (Ticket #'.$ticket->getExtId().')', $error);
-                }
+                $file = array(
+                        'name'  => $a['name'],
+                        'type'  => $a['type'],
+                        'data'  => $this->decode(imap_fetchbody($this->mbox, $mid, $a['index']), $a['encoding'])
+                        );
+                $ticket->importAttachments(array($file), $msgid, 'M');
             }
         }
 
diff --git a/include/class.mailparse.php b/include/class.mailparse.php
index 8d162e52a6d9182b6cca5284ff061538b4124ff9..58adf3eac0210f2b608cda7e1a8e051cd2ffe684 100644
--- a/include/class.mailparse.php
+++ b/include/class.mailparse.php
@@ -15,8 +15,8 @@
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
 
-require_once('Mail/mimeDecode.php');
-require_once('Mail/RFC822.php');
+require_once(PEAR_DIR.'Mail/mimeDecode.php');
+require_once(PEAR_DIR.'Mail/RFC822.php');
 
 class Mail_Parse {
     
@@ -164,6 +164,11 @@ class Mail_Parse {
         return $data;
     }
 
+     
+    function mime_encode($text, $charset=null, $encoding='utf-8') {
+        return Format::encode($text, $charset, $encoding);
+    }
+
     function getAttachments($part=null){
 
         if($part==null)
@@ -173,10 +178,20 @@ class Mail_Parse {
                 && (!strcasecmp($part->disposition,'attachment') 
                     || !strcasecmp($part->disposition,'inline') 
                     || !strcasecmp($part->ctype_primary,'image'))){
+            
             if(!($filename=$part->d_parameters['filename']) && $part->d_parameters['filename*'])
                 $filename=$part->d_parameters['filename*']; //Do we need to decode?
+           
+            $file=array(
+                    'name'  => $filename, 
+                    'type'  => strtolower($part->ctype_primary.'/'.$part->ctype_secondary),
+                    'data'  => $this->mime_encode($part->body, $part->ctype_parameters['charset'])
+                    );
+
+            if(!$this->decode_bodies && $part->headers['content-transfer-encoding'])
+                $file['encoding'] = $part->headers['content-transfer-encoding'];
 
-            return array(array('filename'=>$filename,'body'=>$part->body));
+            return array($file);
         }
 
         $files=array();
@@ -216,4 +231,94 @@ class Mail_Parse {
     function parseAddressList($address){
         return Mail_RFC822::parseAddressList($address, null, null,false);
     }
+
+    function parse($rawemail) {
+        $parser= new Mail_Parse($rawemail);
+        return ($parser && $parser->decode())?$parser:null;
+    }
+}
+
+class EmailDataParser {
+    var $stream;
+    var $error;
+
+    function EmailDataParser($stream=null) {
+        $this->stream = $stream;
+    }
+    
+    function parse($stream) {
+
+        $contents ='';
+        if(is_resource($stream)) {
+            while(!feof($stream))
+                $contents .= fread($stream, 8192);
+
+        } else {
+            $contents = $stream;
+        }
+
+        $parser= new Mail_Parse($contents);
+        if(!$parser->decode()) //Decode...returns false on decoding errors
+            return $this->err('Email parse failed ['.$parser->getError().']');
+        
+        $data =array();
+        //FROM address: who sent the email.
+        if(($fromlist = $parser->getFromAddressList()) && !PEAR::isError($fromlist)) {
+            $from=$fromlist[0]; //Default.
+            foreach($fromlist as $fromobj) {
+                if(!Validator::is_email($fromobj->mailbox.'@'.$fromobj->host)) continue;
+                $from = $fromobj;
+                break;
+            }
+
+            $data['name'] = trim($from->personal,'"');
+            if($from->comment && $from->comment[0])
+                $data['name'].= ' ('.$from->comment[0].')';
+
+            $data['email'] = $from->mailbox.'@'.$from->host;
+        }
+
+        //TO Address:Try to figure out the email address... associated with the incoming email.
+        $emailId = 0;
+        if(($tolist = $parser->getToAddressList())) {
+            foreach ($tolist as $toaddr) {
+                if(($emailId=Email::getIdByEmail($toaddr->mailbox.'@'.$toaddr->host)))
+                    break;
+            }
+        }
+        //maybe we got CC'ed??
+        if(!$emailId && ($cclist=$parser->getCcAddressList())) {
+            foreach ($cclist as $ccaddr) {
+                if(($emailId=Email::getIdByEmail($ccaddr->mailbox.'@'.$ccaddr->host)))
+                    break;
+            }
+        }
+            
+        $data['subject'] = Format::utf8encode($parser->getSubject());
+        $data['message'] = Format::utf8encode(Format::stripEmptyLines($parser->getBody()));
+        $data['header'] = $parser->getHeader();
+        $data['mid'] = $parser->getMessageId();
+        $data['priorityId'] = $parser->getPriority();
+        $data['emailId'] = $emailId;
+
+        //attachments XXX: worry about encoding??
+        $data['attachments'] = $parser->getAttachments();
+
+        return $data;
+    }
+
+    function err($error) {
+        $this->error = $error;
+
+        return false;
+    }
+
+    function getError() {
+        return $this->lastError();
+    }
+
+    function lastError() {
+        return $this->error;
+    }
 }
+?>
diff --git a/include/class.osticket.php b/include/class.osticket.php
index 89fd3603c4f7b876df756157a2aef89e51bdab79..8b297869212d9aac10119b2ade0e3c72e78705c3 100644
--- a/include/class.osticket.php
+++ b/include/class.osticket.php
@@ -149,7 +149,7 @@ class osTicket {
     /* Function expects a well formatted array - see  Format::files()
        It's up to the caller to reject the upload on error.
      */
-    function validateFileUploads(&$files) {
+    function validateFileUploads(&$files, $checkFileTypes=true) {
        
         $errors=0;
         foreach($files as &$file) {
@@ -160,11 +160,12 @@ class osTicket {
                 $file['error'] = 'File upload error #'.$file['error'];
             elseif(!$file['tmp_name'] || !is_uploaded_file($file['tmp_name']))
                 $file['error'] = 'Invalid or bad upload POST';
-            elseif(!$this->isFileTypeAllowed($file))
-                $file['error'] = 'Invalid file type for '.$file['name'];
+            elseif($checkFileTypes && !$this->isFileTypeAllowed($file))
+                $file['error'] = 'Invalid file type for '.Format::htmlchars($file['name']);
             elseif($file['size']>$this->getConfig()->getMaxFileSize())
                 $file['error'] = sprintf('File (%s) is too big. Maximum of %s allowed',
-                        $file['name'], Format::file_size($this->getConfig()->getMaxFileSize()));
+                        Format::htmlchars($file['name']),
+                        Format::file_size($this->getConfig()->getMaxFileSize()));
             
             if($file['error']) $errors++;
         }
diff --git a/include/class.ticket.php b/include/class.ticket.php
index b9857d39a410772698fafe693cc94c12ae639f06..e68f8d4f8c75c0b001d0f3f0cfd66953a69db5cd 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -1379,43 +1379,46 @@ class Ticket {
     }
 
     //Insert message from client
-    function postMessage($message,$source='',$emsgid=null,$headers='',$newticket=false){
+    function postMessage($vars, $source='', $alerts=true) {
         global $cfg;
        
-        if(!$this->getId()) return 0;
-
+        if(!$vars || !$vars['message'])
+            return 0;
+        
         //Strip quoted reply...on emailed replies
         if(!strcasecmp($source, 'Email') 
                 && $cfg->stripQuotedReply() 
-                && ($tag=$cfg->getReplySeparator()) && strpos($message, $tag))
-            list($message)=split($tag, $message);
+                && ($tag=$cfg->getReplySeparator()) && strpos($vars['message'], $tag))
+            list($vars['message']) = split($tag, $vars['message']);
 
         # XXX: Refuse auto-response messages? (via email) XXX: No - but kill our auto-responder.
 
+
         $sql='INSERT INTO '.TICKET_THREAD_TABLE.' SET created=NOW()'
             .' ,thread_type="M" '
             .' ,ticket_id='.db_input($this->getId())
             # XXX: Put Subject header into the 'title' field
-            .' ,body='.db_input(Format::striptags($message)) //Tags/code stripped...meaning client can not send in code..etc
+            .' ,body='.db_input(Format::striptags($vars['message'])) //Tags/code stripped...meaning client can not send in code..etc
             .' ,source='.db_input($source?$source:$_SERVER['REMOTE_ADDR'])
             .' ,ip_address='.db_input($_SERVER['REMOTE_ADDR']);
     
-        if(!db_query($sql) || !($msgid=db_insert_id())) return 0; //bail out....
+        if(!db_query($sql) || !($msgid=db_insert_id()))
+            return 0; //bail out....
 
         $this->setLastMsgId($msgid);
-
-        if ($emsgid !== null) {
+        
+        if (isset($vars['mid'])) {
             $sql='INSERT INTO '.TICKET_EMAIL_INFO_TABLE
                 .' SET message_id='.db_input($msgid)
-                .', email_mid='.db_input($emsgid)
-                .', headers='.db_input($headers);
+                .', email_mid='.db_input($vars['mid'])
+                .', headers='.db_input($vars['header']);
             db_query($sql);
         }
 
-        if($newticket) return $msgid; //Our work is done...
+        if(!$alerts) return $msgid; //Our work is done...
 
         $autorespond = true;
-        if ($autorespond && $headers && TicketFilter::isAutoResponse(Mail_Parse::splitHeaders($headers)))
+        if ($autorespond && $vars['header'] && TicketFilter::isAutoResponse($vars['header']))
             $autorespond=false;
 
         $this->onMessage($autorespond); //must be called b4 sending alerts to staff.
@@ -1431,7 +1434,7 @@ class Ticket {
         //If enabled...send alert to staff (New Message Alert)
         if($cfg->alertONNewMessage() && $tpl && $email && ($msg=$tpl->getNewMessageAlertMsgTemplate())) {
 
-            $msg = $this->replaceVars($msg, array('message' => $message));
+            $msg = $this->replaceVars($msg, array('message' => $vars['message']));
 
             //Build list of recipients and fire the alerts.
             $recipients=array();
@@ -1735,10 +1738,11 @@ class Ticket {
     }
 
     //online based attached files.
-    function uploadAttachments($files, $refid, $type) {
+    function uploadAttachments($files, $refid, $type, $checkFileTypes=false) {
         global $ost;
 
         $uploaded=array();
+        $ost->validateFileUploads($files, $checkFileTypes); //Validator sets errors - if any
         foreach($files as $file) {
             if(!$file['error'] 
                     && ($id=AttachmentFile::upload($file)) 
@@ -1762,8 +1766,44 @@ class Ticket {
         return $uploaded;
     }
 
+    /* Wrapper or uploadAttachments 
+       - used on client interface 
+       - file type check is forced
+       - $_FILES  is passed.
+    */
+    function uploadFiles($files, $refid, $type) {
+        return $this->uploadAttachments(Format::files($files), $refid, $type, true);    
+    }
+
+    /* Emailed & API attachments handler */
+    function importAttachments($attachments, $refid, $type, $checkFileTypes=true) {
+        global $ost;
+
+        if(!$attachments || !is_array($attachments)) return null;
+
+        $files = array();        
+        foreach ($attachments as &$info) {
+            //Do error checking...
+            if ($checkFileTypes && !$ost->isFileTypeAllowed($info))
+                $info['error'] = 'Invalid file type (ext) for '.Format::htmlchars($info['name']);
+            elseif ($info['encoding'] && !strcasecmp($info['encoding'], 'base64')) {
+                if(!($info['data'] = base64_decode($info['data'], true)))
+                    $info['error'] = sprintf('%s: Poorly encoded base64 data', Format::htmlchars($info['name']));
+            }
+
+            if($info['error']) {
+                $this->logNote('File Import Error', $info['error'], 'SYSTEM', false);
+            } elseif (($id=$this->saveAttachment($info, $refid, $type))) {
+                $files[] = $id;
+            }
+        }
+
+        return $files;
+    }
+
+
     /*
-       Save attachment to the DB. uploads (above), email or json/xml.
+       Save attachment to the DB. upload/import (above).
        
        @file is a mixed var - can be ID or file hash.
      */
@@ -2182,7 +2222,7 @@ class Ticket {
 
         $dept = $ticket->getDept();
         //post the message.
-        $msgid=$ticket->postMessage($vars['message'],$source,$vars['mid'],$vars['header'],true);
+        $msgid=$ticket->postMessage($vars , $source, false);
 
         // Configure service-level-agreement for this ticket
         $ticket->selectSLAId($vars['slaId']);
diff --git a/include/staff/apikey.inc.php b/include/staff/apikey.inc.php
index 6e2ffe6e6d8fdc4d41134726e6853162f3bfe0da..507753aae6c3852d3c93a638901470b83cd8c295 100644
--- a/include/staff/apikey.inc.php
+++ b/include/staff/apikey.inc.php
@@ -71,13 +71,23 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         <?php } ?>
         <tr>
             <th colspan="2">
-                <em><strong>Enabled Services:</strong>: Check applicable API services. All active keys can make cron call.</em>
+                <em><strong>Services:</strong>: Check applicable API services enabled for the key.</em>
             </th>
         </tr>
         <tr>
             <td colspan=2 style="padding-left:5px">
-                <input type="checkbox" name="can_create_tickets" value="1" <?php echo $info['can_create_tickets']?'checked="checked"':''; ?> >
-                Can Create Tickets. <em>(XML/JSON/PIPE)</em>
+                <label>
+                    <input type="checkbox" name="can_create_tickets" value="1" <?php echo $info['can_create_tickets']?'checked="checked"':''; ?> >
+                    Can Create Tickets <em>(XML/JSON/EMAIL)</em>
+                </label>
+            </td>
+        </tr>
+        <tr>
+            <td colspan=2 style="padding-left:5px">
+                <label>
+                    <input type="checkbox" name="can_exec_cron" value="1" <?php echo $info['can_exec_cron']?'checked="checked"':''; ?> >
+                    Can Execute Cron
+                </label>
             </td>
         </tr>
         <tr>
diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php
index f8674095d49cfe25102ef682e64bb9df59e1614a..b7175a06e6b9c5b6768690de5d233bdd6a43f5fc 100644
--- a/include/staff/settings-emails.inc.php
+++ b/include/staff/settings-emails.inc.php
@@ -61,19 +61,13 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config)
                 &nbsp;&nbsp;<em>(System administrator's email)</em> 
             </td>
         </tr>
-        <tr><th colspan=2><em><strong>Incoming Emails</strong>: For mail fetcher (polling) to work you must set an external cron job or enable auto-cron</em></th>
+        <tr><th colspan=2><em><strong>Incoming Emails</strong>: For mail fetcher (polling) to work you must set an external cron job or enable auto-cron polling</em></th>
         <tr>
             <td width="180">Email Polling:</td>
             <td><input type="checkbox" name="enable_mail_polling" value=1 <?php echo $config['enable_mail_polling']? 'checked="checked"': ''; ?>  > Enable POP/IMAP polling
                  &nbsp;&nbsp;
                  <input type="checkbox" name="enable_auto_cron" <?php echo $config['enable_auto_cron']?'checked="checked"':''; ?>>
-                 Enable Auto-Cron <em>(Poll based on staff activity - NOT recommended)</em>
-            </td>
-        </tr>
-        <tr>
-            <td width="180">Email Piping:</td>
-            <td><input type="checkbox" name="enable_email_piping" value=1 <?php echo $config['enable_email_piping']? 'checked="checked"': ''; ?>> Enable email piping
-                 &nbsp;&nbsp;<em>(You pipe we accept policy)</em>
+                 Poll on auto-cron <em>(Poll based on staff activity - NOT recommended)</em>
             </td>
         </tr>
         <tr>
diff --git a/include/upgrader/sql/c0fd16f4-959a00e.patch.sql b/include/upgrader/sql/c0fd16f4-959a00e.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..186dfc5bbbed284c9642fd1b7b20c94356f6af08
--- /dev/null
+++ b/include/upgrader/sql/c0fd16f4-959a00e.patch.sql
@@ -0,0 +1,17 @@
+/**
+ * @version v1.7
+ *
+ * @schema d959a00e55c75e0c903b9e37324fd25d
+ */
+
+-- Add cron exec service
+ALTER TABLE  `%TABLE_PREFIX%api_key`
+    ADD  `can_exec_cron` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT  '1' AFTER  `can_create_tickets`;
+
+-- Drop email piping settings from config table.
+ALTER TABLE  `%TABLE_PREFIX%config` 
+    DROP  `enable_email_piping`;
+
+-- update schema signature
+UPDATE `%TABLE_PREFIX%config`
+    SET `schema_signature`='d959a00e55c75e0c903b9e37324fd25d';
diff --git a/main.inc.php b/main.inc.php
index 2dc651c9979d9d3cdda518efeb274f49e1d57d37..2e7691f2b088511e31e85554579b57f5a5e99a75 100644
--- a/main.inc.php
+++ b/main.inc.php
@@ -63,7 +63,7 @@
 
     #Current version && schema signature (Changes from version to version)
     define('THIS_VERSION','1.7-RC4+'); //Shown on admin panel
-    define('SCHEMA_SIGNATURE', 'c0fd16f4eaf99b920be9f7fc6ebead32'); //MD5 signature of the db schema. (used to trigger upgrades)
+    define('SCHEMA_SIGNATURE', 'd959a00e55c75e0c903b9e37324fd25d'); //MD5 signature of the db schema. (used to trigger upgrades)
     #load config info
     $configfile='';
     if(file_exists(ROOT_DIR.'ostconfig.php')) //Old installs prior to v 1.6 RC5
diff --git a/open.php b/open.php
index a7f064fb9ca58d26f4827b0abb8537df8ef4c3b2..e6f5b52970b06a94facc3044dda307375328f018 100644
--- a/open.php
+++ b/open.php
@@ -33,12 +33,8 @@ if($_POST):
     if(($ticket=Ticket::create($_POST,$errors,SOURCE))){
         $msg='Support ticket request created';
         //Upload attachments...         
-        if($cfg->allowOnlineAttachments()
-                && $_FILES['attachments']
-                && ($files=Format::files($_FILES['attachments']))) {
-            $ost->validateFileUploads($files); //Validator sets errors - if any.
-            $ticket->uploadAttachments($files, $ticket->getLastMsgId(), 'M');
-        }
+        if($cfg->allowOnlineAttachments() && $_FILES['attachments'])
+            $ticket->uploadFiles($_FILES['attachments'], $ticket->getLastMsgId(), 'M');
 
         //Logged in...simply view the newly created ticket.
         if($thisclient && $thisclient->isValid()) {
diff --git a/setup/scripts/cleanup-codebase.sh b/setup/cli/cleanup-codebase.sh
similarity index 98%
rename from setup/scripts/cleanup-codebase.sh
rename to setup/cli/cleanup-codebase.sh
index d9209b91738f3cb8b1f690bdd1d0db6fdf45a481..40586b18c593ca46036882e9267010a2fd23900b 100644
--- a/setup/scripts/cleanup-codebase.sh
+++ b/setup/cli/cleanup-codebase.sh
@@ -39,6 +39,7 @@ images/ticket_status_title.jpg
 include/settings.php
 
 # Removed in 1.7.0
+api/urls.conf.php
 images/bg.gif
 images/fibres.png
 images/home.gif
@@ -57,6 +58,7 @@ images/ticket_status_icon.jpg
 images/verticalbar.jpg
 images/view_closed_btn.gif
 images/view_open_btn.gif
+include/api.ticket.php
 include/class.msgtpl.php
 include/class.sys.php
 include/client/index.php
diff --git a/setup/scripts/manage.php b/setup/cli/manage.php
similarity index 100%
rename from setup/scripts/manage.php
rename to setup/cli/manage.php
diff --git a/setup/scripts/modules/class.module.php b/setup/cli/modules/class.module.php
similarity index 100%
rename from setup/scripts/modules/class.module.php
rename to setup/cli/modules/class.module.php
diff --git a/setup/scripts/modules/unpack.php b/setup/cli/modules/unpack.php
similarity index 100%
rename from setup/scripts/modules/unpack.php
rename to setup/cli/modules/unpack.php
diff --git a/setup/scripts/package.php b/setup/cli/package.php
similarity index 95%
rename from setup/scripts/package.php
rename to setup/cli/package.php
index 22542b6e0fe9d7dc944af1839c91b473047e4e9d..9b5ecae51e212cc570b220c36b75935f75b79b93 100755
--- a/setup/scripts/package.php
+++ b/setup/cli/package.php
@@ -115,6 +115,12 @@ package("setup/inc/sql/*.{sql,md5}", "upload/setup/inc/sql", -1);
 # Load the license and documentation
 package("*.{txt,md}", "");
 
+#Rename markdown as text TODO: Do html version before rename.
+if(($mds = glob("$stage_path/*.md"))) {
+    foreach($mds as $md)
+        rename($md, preg_replace('/\.md$/', '.txt', $md));
+}
+
 # Make an archive of the stage folder
 $version_info = preg_grep('/THIS_VERSION/',
     explode("\n", file_get_contents("$root/main.inc.php")));
diff --git a/setup/inc/sql/osTicket-mysql.sql b/setup/inc/sql/osTicket-mysql.sql
index ba980b876cb9038a4f9a559037fad104d85b312d..3d14f86d5231b4c05986e5677d62adda544e9d56 100644
--- a/setup/inc/sql/osTicket-mysql.sql
+++ b/setup/inc/sql/osTicket-mysql.sql
@@ -6,6 +6,7 @@ CREATE TABLE `%TABLE_PREFIX%api_key` (
   `ipaddr` varchar(64) NOT NULL,
   `apikey` varchar(255) NOT NULL,
   `can_create_tickets` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT  '1',
+  `can_exec_cron` TINYINT( 1 ) UNSIGNED NOT NULL DEFAULT  '1',
   `notes` text,
   `updated` datetime NOT NULL,
   `created` datetime NOT NULL,
@@ -97,7 +98,6 @@ CREATE TABLE `%TABLE_PREFIX%config` (
   `enable_captcha` tinyint(1) unsigned NOT NULL default '0',
   `enable_auto_cron` tinyint(1) unsigned NOT NULL default '0',
   `enable_mail_polling` tinyint(1) unsigned NOT NULL default '0',
-  `enable_email_piping` tinyint(1) unsigned NOT NULL default '0',
   `send_sys_errors` tinyint(1) unsigned NOT NULL default '1',
   `send_sql_errors` tinyint(1) unsigned NOT NULL default '1',
   `send_mailparse_errors` tinyint(1) unsigned NOT NULL default '1',
diff --git a/setup/inc/sql/osTicket-mysql.sql.md5 b/setup/inc/sql/osTicket-mysql.sql.md5
index db25b4569b60dcd93349db6f4f51cd58047ef807..d14b842c7046800622e9423f6cb8f2bc24a0036b 100644
--- a/setup/inc/sql/osTicket-mysql.sql.md5
+++ b/setup/inc/sql/osTicket-mysql.sql.md5
@@ -1 +1 @@
-c0fd16f4eaf99b920be9f7fc6ebead32
+d959a00e55c75e0c903b9e37324fd25d
diff --git a/setup/scripts/automail.php b/setup/scripts/automail.php
new file mode 100755
index 0000000000000000000000000000000000000000..9160cb810c0cac6f6d7154ea678b474efbabbdd8
--- /dev/null
+++ b/setup/scripts/automail.php
@@ -0,0 +1,80 @@
+#!/usr/bin/php -q
+<?php
+/*********************************************************************
+    automail.php
+
+    PHP script used for remote email piping...same as as the perl version.
+
+    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:
+**********************************************************************/
+
+# Configuration: Enter the url and key. That is it.
+#  url => URL to api/tickets.email e.g http://yourdomain.com/support/api/tickets.email
+#  key => API's Key (see admin panel on how to generate a key)
+#   
+
+$config = array(
+        'url'=>'http://yourdomain.com/support/api/tickets.email',
+        'key'=>'API KEY HERE'
+        );
+
+#pre-checks
+function_exists('file_get_contents') or die('upgrade php >=4.3');
+function_exists('curl_version') or die('CURL support required');
+#read stdin (piped email)
+$data=file_get_contents('php://stdin') or die('Error reading stdin. No message');
+
+#set timeout
+set_time_limit(10);
+
+#curl post
+$ch = curl_init();        
+curl_setopt($ch, CURLOPT_URL, $config['url']);        
+curl_setopt($ch, CURLOPT_POST, 1);        
+curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
+curl_setopt($ch, CURLOPT_USERAGENT, 'osTicket API Client v1.7');
+curl_setopt($ch, CURLOPT_HEADER, TRUE);
+curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Expect:', 'X-API-Key: '.$config['key']));
+curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 
+$result=curl_exec($ch);        
+curl_close($ch);
+
+//Use postfix exit codes...expected by MTA.
+$code = 75;
+if(preg_match('/HTTP\/.* ([0-9]+) .*/', $result, $status)) {
+    switch($status[1]) {
+        case 201: //Success
+            $code = 0;
+            break;
+        case 400:
+            $code = 66;
+            break;
+        case 401: /* permission denied */
+        case 403:
+            $code = 77;
+            break;
+        case 415:
+        case 416:
+        case 417:
+        case 501:
+            $code = 65;
+            break;
+        case 503:
+            $code = 69;
+            break;
+        case 500: //Server error.
+        default: //Temp (unknown) failure - retry 
+            $code = 75;
+    }
+}
+
+exit($code);
+?>
diff --git a/setup/scripts/automail.pl b/setup/scripts/automail.pl
new file mode 100755
index 0000000000000000000000000000000000000000..9f55612b82e740445a4ef3e51f56fa7d443cb709
--- /dev/null
+++ b/setup/scripts/automail.pl
@@ -0,0 +1,60 @@
+#!/usr/bin/perl
+#######################################################################
+#    automail.pl
+#
+#    Perl script used for remote email piping...same as as the PHP version.
+#
+#    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:
+#######################################################################
+
+#Configuration: Enter the url and key. That is it.
+#  url=> URL to pipe.php e.g http://yourdomain.com/support/api/tickets.email
+#  key=> API Key (see admin panel on how to generate a key)
+
+%config = (url => 'http://yourdomain.com/support/api/tickets.email',
+           key => 'API KEY HERE');
+
+#Get piped message from stdin
+while (<STDIN>) {
+    $rawemail .= $_;
+}
+
+use LWP::UserAgent;
+$ua = LWP::UserAgent->new;
+
+$ua->agent('osTicket API Client v1.7');
+$ua->default_header('X-API-Key' => $config{'key'});
+$ua->timeout(10);
+
+use HTTP::Request::Common qw(POST);
+
+my $enc ='text/plain';
+my $req = (POST $config{'url'}, Content_Type => $enc, Content => $rawemail);
+$response = $ua->request($req);
+
+#
+# Process response
+# Add exit codes - depending on what your  MTA expects.
+# By default postfix exit codes are used - which are standard for MTAs.
+#
+    
+use Switch;
+
+$code = 75;    
+switch($response->code) {
+    case 201 { $code = 0; }
+    case 400 { $code = 66; }
+    case [401,403] { $code = 77; }
+    case [415,416,417,501] { $code = 65; }
+    case 503 { $code = 69 }
+    case 500 { $code = 75 }
+}
+#print "RESPONSE: ". $response->code. ">>>".$code;
+exit $code;
diff --git a/setup/scripts/rcron.php b/setup/scripts/rcron.php
new file mode 100755
index 0000000000000000000000000000000000000000..53c2da007f91ab84007afcc20409b32a8da822ed
--- /dev/null
+++ b/setup/scripts/rcron.php
@@ -0,0 +1,52 @@
+#!/usr/bin/php -q
+<?php
+/*********************************************************************
+    rcron.php
+
+    PHP script used for remote cron calls.
+
+    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:
+**********************************************************************/
+
+# Configuration: Enter the url and key. That is it.
+#  url => URL to api/task/cron e.g http://yourdomain.com/support/api/task/cron
+#  key => API's Key (see admin panel on how to generate a key)
+#
+
+$config = array(
+        'url'=>'http://yourdomain.com/support/api/task/cron',
+        'key'=>'API KEY HERE'
+        );
+
+#pre-checks
+function_exists('curl_version') or die('CURL support required');
+
+#set timeout
+set_time_limit(30);
+
+#curl post
+$ch = curl_init();        
+curl_setopt($ch, CURLOPT_URL, $config['url']);        
+curl_setopt($ch, CURLOPT_POST, 1);
+curl_setopt($ch, CURLOPT_POSTFIELDS, '');
+curl_setopt($ch, CURLOPT_USERAGENT, 'osTicket API Client v1.7');
+curl_setopt($ch, CURLOPT_HEADER, TRUE);
+curl_setopt($ch, CURLOPT_HTTPHEADER, array( 'Expect:', 'X-API-Key: '.$config['key']));
+curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
+$result=curl_exec($ch);        
+curl_close($ch);
+
+if(preg_match('/HTTP\/.* ([0-9]+) .*/', $result, $status) && $status[1] == 200)
+    exit(0);
+
+echo $result;
+exit(1);
+?>
diff --git a/tickets.php b/tickets.php
index 4ee69a8ef51f5b2b274df224fac4ed195330da5a..cc213787703521d3afb88c79715583d1842487cb 100644
--- a/tickets.php
+++ b/tickets.php
@@ -40,13 +40,12 @@ if($_POST && is_object($ticket) && $ticket->getId()):
 
         if(!$errors) {
             //Everything checked out...do the magic.
-            if(($msgid=$ticket->postMessage($_POST['message'],'Web'))) {
-                if($cfg->allowOnlineAttachments() 
-                        && $_FILES['attachments']
-                        && ($files=Format::files($_FILES['attachments']))) {
-                    $ost->validateFileUploads($files); //Validator sets errors - if any.
-                    $ticket->uploadAttachments($files, $msgid, 'M');
-                }
+            if(($msgid=$ticket->postMessage(array('message'=>$_POST['message']), 'Web'))) {
+    
+                //Upload files
+                if($cfg->allowOnlineAttachments() && $_FILES['attachments'])
+                    $ticket->uploadFiles($_FILES['attachments'], $msgid, 'M');
+
                 $msg='Message Posted Successfully';
             } else {
                 $errors['err']='Unable to post the message. Try again';