diff --git a/include/api.tickets.php b/include/api.tickets.php index f0758d5d2ea75324620ce12acbf30fe76222fb06..00d7628b2e06076938a13df268b0a52fe05dbdc3 100644 --- a/include/api.tickets.php +++ b/include/api.tickets.php @@ -39,7 +39,7 @@ class TicketApiController extends ApiController { if(!strcasecmp($format, 'email')) { $supported = array_merge($supported, array('header', 'mid', 'emailId', 'ticketId', 'reply-to', 'reply-to-name', - 'in-reply-to', 'references')); + 'in-reply-to', 'references', 'thread-type')); $supported['attachments']['*'][] = 'cid'; } @@ -144,10 +144,6 @@ class TicketApiController extends ApiController { function processEmail() { $data = $this->getEmailRequest(); - if($data['ticketId'] && ($ticket=Ticket::lookup($data['ticketId']))) { - if(($msgid=$ticket->postMessage($data, 'Email'))) - return $ticket; - } if (($thread = ThreadEntry::lookupByEmailHeaders($data)) && $thread->postEmail($data)) { diff --git a/include/class.mailer.php b/include/class.mailer.php index 21a7d157d68a28191b88e798dade0633d3c15725..f3aa6e43ea9c5f0d8debcf0aaee1e29824d5fa2c 100644 --- a/include/class.mailer.php +++ b/include/class.mailer.php @@ -109,7 +109,8 @@ class Mailer { 'Subject' => $subject, 'Date'=> date('D, d M Y H:i:s O'), 'Message-ID' => $messageId, - 'X-Mailer' =>'osTicket Mailer' + 'X-Mailer' =>'osTicket Mailer', + 'Return-Path' => $this->getEmail()->getEmail(), ); //Set bulk/auto-response headers. diff --git a/include/class.mailfetch.php b/include/class.mailfetch.php index 7149d44d05da615b7a0a74699ef94cc2842f5e69..e7b039211f71b9c8ff8f472503ca75eed20e3924 100644 --- a/include/class.mailfetch.php +++ b/include/class.mailfetch.php @@ -311,7 +311,7 @@ class MailFetcher { } //search for specific mime type parts....encoding is the desired encoding. - function getPart($mid, $mimeType, $encoding=false, $struct=null, $partNumber=false) { + function getPart($mid, $mimeType, $encoding=false, $struct=null, $partNumber=false, $recurse=-1) { if(!$struct && $mid) $struct=@imap_fetchstructure($this->mbox, $mid); @@ -345,11 +345,12 @@ class MailFetcher { //Do recursive search $text=''; - if($struct && $struct->parts) { + if($struct && $struct->parts && $recurse) { while(list($i, $substruct) = each($struct->parts)) { if($partNumber) $prefix = $partNumber . '.'; - if(($result=$this->getPart($mid, $mimeType, $encoding, $substruct, $prefix.($i+1)))) + if (($result=$this->getPart($mid, $mimeType, $encoding, + $substruct, $prefix.($i+1), $recurse-1))) $text.=$result; } } @@ -437,6 +438,46 @@ class MailFetcher { return imap_fetchheader($this->mbox, $mid,FT_PREFETCHTEXT); } + function isBounceNotice($mid) { + if (!($body = $this->getPart($mid, 'message/delivery-status'))) + return false; + + $info = Mail_Parse::splitHeaders($body); + if (!isset($info['Action'])) + return false; + + return strcasecmp($info['Action'], 'failed') === 0; + } + + function getDeliveryStatusMessage($mid) { + if (!($struct = @imap_fetchstructure($this->mbox, $mid))) + return false; + + $ctype = $this->getMimeType($struct); + if (strtolower($ctype) == 'multipart/report') { + foreach ($struct->parameters as $p) { + if (strtolower($p->attribute) == 'report-type' + && $p->value == 'delivery-status') { + return sprintf('<pre>%s</pre>', + Format::htmlchars( + $this->getPart($mid, 'text/plain', $this->charset, $struct, false, 1) + )); + } + } + } + return false; + } + + function getOriginalMessage($mid) { + if (!($body = $this->getPart($mid, 'message/rfc822'))) + return null; + + $msg = new Mail_Parse($body); + if (!$msg->decode()) + return null; + + return $msg->struct; + } function getPriority($mid) { return Mail_Parse::parsePriority($this->getHeader($mid)); @@ -485,9 +526,22 @@ class MailFetcher { } $vars = $mailinfo; + if ($this->isBounceNotice($mid)) { + // Fetch the original References and assign to 'references' + if ($msg = $this->getOriginalMessage($mid)) { + $vars['references'] = $msg->headers['references']; + unset($vars['in-reply-to']); + } + // Fetch deliver status report + $vars['message'] = $this->getDeliveryStatusMessage($mid); + $vars['thread-type'] = 'N'; + } + else { + $vars['message']=Format::stripEmptyLines($this->getBody($mid)); + } + $vars['name']=$this->mime_decode($mailinfo['name']); $vars['subject']=$mailinfo['subject']?$this->mime_decode($mailinfo['subject']):'[No Subject]'; - $vars['message']=Format::stripEmptyLines($this->getBody($mid)); $vars['emailId']=$mailinfo['emailId']?$mailinfo['emailId']:$this->getEmailId(); //Missing FROM name - use email address. diff --git a/include/class.mailparse.php b/include/class.mailparse.php index 32b0083a6df2c077defd19ac1560627b20ab877c..3356cb9bf1a596272d3e46e9a2cb72dfc0c41ca5 100644 --- a/include/class.mailparse.php +++ b/include/class.mailparse.php @@ -169,6 +169,40 @@ class Mail_Parse { return Mail_Parse::parseAddressList($header); } + function isBounceNotice() { + if (!($body = $this->getPart($this->struct, 'message/delivery-status'))) + return false; + + $info = self::splitHeaders($body); + if (!isset($info['Action'])) + return false; + + return strcasecmp($info['Action'], 'failed') === 0; + } + + function getDeliveryStatusMessage() { + $ctype = @strtolower($this->struct->ctype_primary.'/'.$this->struct->ctype_secondary); + if ($ctype == 'multipart/report' + && isset($this->struct->ctype_parameters['report-type']) + && $this->struct->ctype_parameters['report-type'] == 'delivery-status' + ) { + return sprintf('<pre>%s</pre>', + Format::htmlchars( + $this->getPart($this->struct, 'text/plain', 1) + )); + } + return false; + } + + function getOriginalMessage() { + foreach ($this->struct->parts as $p) { + $ctype = $p->ctype_primary.'/'.$p->ctype_secondary; + if (strtolower($ctype) === 'message/rfc822') + return $p->parts[0]; + } + return null; + } + function getBody(){ global $cfg; @@ -197,7 +231,7 @@ class Mail_Parse { return $body; } - function getPart($struct, $ctypepart) { + function getPart($struct, $ctypepart, $recurse=-1) { if($struct && !$struct->parts) { $ctype = @strtolower($struct->ctype_primary.'/'.$struct->ctype_secondary); @@ -213,9 +247,10 @@ class Mail_Parse { } $data=''; - if($struct && $struct->parts) { + if($struct && $struct->parts && $recurse) { foreach($struct->parts as $i=>$part) { - if($part && !$part->disposition && ($text=$this->getPart($part,$ctypepart))) + if($part && !$part->disposition + && ($text=$this->getPart($part,$ctypepart,$recurse - 1))) $data.=$text; } } @@ -393,16 +428,27 @@ class EmailDataParser { } } + if ($parser->isBounceNotice()) { + // Fetch the original References and assign to 'references' + if ($msg = $parser->getOriginalMessage()) + $data['references'] = $msg->headers['references']; + // Fetch deliver status report + $data['message'] = $parser->getDeliveryStatusMessage(); + $data['thread-type'] = 'N'; + } + else { + // Typical email + $data['message'] = Format::stripEmptyLines($parser->getBody()); + $data['in-reply-to'] = $parser->struct->headers['in-reply-to']; + $data['references'] = $parser->struct->headers['references']; + } + $data['subject'] = $parser->getSubject(); - $data['message'] = Format::stripEmptyLines($parser->getBody()); $data['header'] = $parser->getHeader(); $data['mid'] = $parser->getMessageId(); $data['priorityId'] = $parser->getPriority(); $data['emailId'] = $emailId; - $data['in-reply-to'] = $parser->struct->headers['in-reply-to']; - $data['references'] = $parser->struct->headers['references']; - if (($replyto = $parser->getReplyTo()) && !PEAR::isError($replyto)) { $replyto = $replyto[0]; $data['reply-to'] = $replyto->mailbox.'@'.$replyto->host; diff --git a/include/class.thread.php b/include/class.thread.php index 89a5d0af571a9645e39b570c147810ba5065632f..2fda4bcae682487687a6ea162a2f32454a82dd24 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -575,6 +575,7 @@ Class ThreadEntry { 'ip' => '', 'reply_to' => $this, ); + $errors = array(); if (isset($mailinfo['attachments'])) $vars['attachments'] = $mailinfo['attachments']; @@ -591,7 +592,6 @@ Class ThreadEntry { elseif ($staff_id = Staff::getIdByEmail($mailinfo['email'])) { $vars['staffId'] = $staff_id; $poster = Staff::lookup($staff_id); - $errors = array(); $vars['note'] = $body; return $ticket->postNote($vars, $errors, $poster); } @@ -599,6 +599,15 @@ Class ThreadEntry { // Don't process the email -- it came FROM this system return true; } + // Support the mail parsing system declaring a thread-type + elseif (isset($mailinfo['thread-type'])) { + switch ($mailinfo['thread-type']) { + case 'N': + $vars['note'] = $body; + $poster = $mailinfo['email']; + return $ticket->postNote($vars, $errors, $poster); + } + } // TODO: Consider security constraints else { $vars['message'] = sprintf("Received From: %s\n\n%s",