Skip to content

Commit

Permalink
Merge pull request osTicket#461 from protich/feature/api_revisited
Browse files Browse the repository at this point in the history
Feature/api revisited
  • Loading branch information
protich committed Feb 18, 2013
2 parents dd92a06 + f226f71 commit e343010
Show file tree
Hide file tree
Showing 31 changed files with 805 additions and 417 deletions.
69 changes: 2 additions & 67 deletions api/api.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>
Copyright (c) 2006-2012 osTicket
Expand All @@ -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.
}
?>
10 changes: 6 additions & 4 deletions api/cron.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/*********************************************************************
cron.php
File to handle cron job calls (local and remote).
File to handle LOCAL cron job calls.
Peter Rotich <[email protected]>
Copyright (c) 2006-2012 osTicket
Expand All @@ -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();
?>
19 changes: 11 additions & 8 deletions api/http.php
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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']);
?>
126 changes: 9 additions & 117 deletions api/pipe.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>
Copyright (c) 2006-2012 osTicket
Expand All @@ -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();
?>
11 changes: 0 additions & 11 deletions api/urls.conf.php

This file was deleted.

43 changes: 43 additions & 0 deletions include/api.cron.php
Original file line number Diff line number Diff line change
@@ -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();
}
}
?>
Loading

0 comments on commit e343010

Please sign in to comment.