diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAReceiveController.php b/src/applications/metamta/controller/PhabricatorMetaMTAReceiveController.php
index db8168d12b..d113e776ac 100644
--- a/src/applications/metamta/controller/PhabricatorMetaMTAReceiveController.php
+++ b/src/applications/metamta/controller/PhabricatorMetaMTAReceiveController.php
@@ -1,101 +1,101 @@
getRequest();
$user = $request->getUser();
if ($request->isFormPost()) {
$received = new PhabricatorMetaMTAReceivedMail();
$header_content = array(
'Message-ID' => Filesystem::readRandomCharacters(12),
);
$from = $request->getStr('sender');
$to = $request->getStr('receiver');
$uri = '/mail/received/';
if (!empty($from)) {
$header_content['from'] = $from;
}
if (preg_match('/.+@.+/', $to)) {
$header_content['to'] = $to;
} else {
$receiver = PhabricatorMetaMTAReceivedMail::loadReceiverObject($to);
if (!$receiver) {
throw new Exception(pht("No such task or revision!"));
}
- $hash = PhabricatorMetaMTAReceivedMail::computeMailHash(
- $receiver->getMailKey(),
- $user->getPHID());
+ $hash = PhabricatorObjectMailReceiver::computeMailHash(
+ $receiver->getMailKey(),
+ $user->getPHID());
$header_content['to'] =
$to.'+'.$user->getID().'+'.$hash.'@';
}
$received->setHeaders($header_content);
$received->setBodies(
array(
'text' => $request->getStr('body'),
));
$received->save();
$received->processReceivedMail();
return id(new AphrontRedirectResponse())->setURI($uri);
}
$form = new AphrontFormView();
$form->setUser($request->getUser());
$form->setAction($this->getApplicationURI('/receive/'));
$form
->appendChild(hsprintf(
'
%s
',
pht(
'This form will simulate sending mail to an object '.
'or an email address.')))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('From'))
->setName('sender'))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('To'))
->setName('receiver')
->setCaption(pht(
'e.g. %s or %s',
phutil_tag('tt', array(), 'D1234'),
phutil_tag('tt', array(), 'bugs@example.com'))))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel(pht('Body'))
->setName('body'))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Receive Mail')));
$panel = new AphrontPanelView();
$panel->setHeader(pht('Receive Email'));
$panel->appendChild($form);
$panel->setNoBackground();
$nav = $this->buildSideNavView();
$nav->selectFilter('receive');
$nav->appendChild($panel);
return $this->buildApplicationPage(
$nav,
array(
'title' => pht('Receive Test'),
'device' => true,
));
}
}
diff --git a/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php b/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php
index 893e7c36f4..2fb0632be5 100644
--- a/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php
+++ b/src/applications/metamta/receiver/PhabricatorObjectMailReceiver.php
@@ -1,37 +1,43 @@
getAddressRegexp();
foreach ($mail->getToAddresses() as $address) {
$address = self::stripMailboxPrefix($address);
$local = id(new PhutilEmailAddress($address))->getLocalPart();
if (preg_match($regexp, $local)) {
return true;
}
}
return false;
}
private function getAddressRegexp() {
$pattern = $this->getObjectPattern();
$regexp =
'(^'.
'(?P'.$pattern.')'.
'\\+'.
'(?P\w+)'.
'\\+'.
'(?P[a-f0-9]{16})'.
'$)U';
return $regexp;
}
+ public static function computeMailHash($mail_key, $phid) {
+ $global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key');
+
+ $hash = PhabricatorHash::digest($mail_key.$global_mail_key.$phid);
+ return substr($hash, 0, 16);
+ }
}
diff --git a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
index 67d8550edc..7bae7e51a6 100644
--- a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
+++ b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
@@ -1,328 +1,328 @@
validateMailReceiver($mail_receiver);
$this->mailReceiver = $mail_receiver;
return $this;
}
final public function getMailReceiver() {
return $this->mailReceiver;
}
final public function setActor(PhabricatorUser $actor) {
$this->actor = $actor;
return $this;
}
final public function getActor() {
return $this->actor;
}
final public function setExcludeMailRecipientPHIDs(array $exclude) {
$this->excludePHIDs = $exclude;
return $this;
}
final public function getExcludeMailRecipientPHIDs() {
return $this->excludePHIDs;
}
abstract public function validateMailReceiver($mail_receiver);
abstract public function getPrivateReplyHandlerEmailAddress(
PhabricatorObjectHandle $handle);
public function getReplyHandlerDomain() {
return PhabricatorEnv::getEnvConfig(
'metamta.reply-handler-domain');
}
abstract public function getReplyHandlerInstructions();
abstract protected function receiveEmail(
PhabricatorMetaMTAReceivedMail $mail);
public function processEmail(PhabricatorMetaMTAReceivedMail $mail) {
$error = $this->sanityCheckEmail($mail);
if ($error) {
if ($this->shouldSendErrorEmail($mail)) {
$this->sendErrorEmail($error, $mail);
}
return null;
}
return $this->receiveEmail($mail);
}
private function sanityCheckEmail(PhabricatorMetaMTAReceivedMail $mail) {
$body = $mail->getCleanTextBody();
$attachments = $mail->getAttachments();
if (empty($body) && empty($attachments)) {
return 'Empty email body. Email should begin with an !action and / or '.
'text to comment. Inline replies and signatures are ignored.';
}
return null;
}
/**
* Only send an error email if the user is talking to just Phabricator. We
* can assume if there is only one To address it is a Phabricator address
* since this code is running and everything.
*/
private function shouldSendErrorEmail(PhabricatorMetaMTAReceivedMail $mail) {
return (count($mail->getToAddresses()) == 1) &&
(count($mail->getCCAddresses()) == 0);
}
private function sendErrorEmail($error,
PhabricatorMetaMTAReceivedMail $mail) {
$template = new PhabricatorMetaMTAMail();
$template->setSubject('Exception: unable to process your mail request');
$template->setBody($this->buildErrorMailBody($error, $mail));
$template->setRelatedPHID($mail->getRelatedPHID());
$phid = $this->getActor()->getPHID();
$tos = array(
$phid => PhabricatorObjectHandleData::loadOneHandle(
$phid,
// TODO: This could be cleaner (T603).
PhabricatorUser::getOmnipotentUser()),
);
$mails = $this->multiplexMail($template, $tos, array());
foreach ($mails as $email) {
$email->saveAndSend();
}
return true;
}
private function buildErrorMailBody($error,
PhabricatorMetaMTAReceivedMail $mail) {
$original_body = $mail->getRawTextBody();
$main_body = <<addRawSection($main_body);
$body->addReplySection($this->getReplyHandlerInstructions());
return $body->render();
}
public function supportsPrivateReplies() {
return (bool)$this->getReplyHandlerDomain() &&
!$this->supportsPublicReplies();
}
public function supportsPublicReplies() {
if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
return false;
}
if (!$this->getReplyHandlerDomain()) {
return false;
}
return (bool)$this->getPublicReplyHandlerEmailAddress();
}
final public function supportsReplies() {
return $this->supportsPrivateReplies() ||
$this->supportsPublicReplies();
}
public function getPublicReplyHandlerEmailAddress() {
return null;
}
final public function getRecipientsSummary(
array $to_handles,
array $cc_handles) {
assert_instances_of($to_handles, 'PhabricatorObjectHandle');
assert_instances_of($cc_handles, 'PhabricatorObjectHandle');
$body = '';
if (PhabricatorEnv::getEnvConfig('metamta.recipients.show-hints')) {
if ($to_handles) {
$body .= "To: ".implode(', ', mpull($to_handles, 'getName'))."\n";
}
if ($cc_handles) {
$body .= "Cc: ".implode(', ', mpull($cc_handles, 'getName'))."\n";
}
}
return $body;
}
final public function multiplexMail(
PhabricatorMetaMTAMail $mail_template,
array $to_handles,
array $cc_handles) {
assert_instances_of($to_handles, 'PhabricatorObjectHandle');
assert_instances_of($cc_handles, 'PhabricatorObjectHandle');
$result = array();
// If MetaMTA is configured to always multiplex, skip the single-email
// case.
if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
// If private replies are not supported, simply send one email to all
// recipients and CCs. This covers cases where we have no reply handler,
// or we have a public reply handler.
if (!$this->supportsPrivateReplies()) {
$mail = clone $mail_template;
$mail->addTos(mpull($to_handles, 'getPHID'));
$mail->addCCs(mpull($cc_handles, 'getPHID'));
if ($this->supportsPublicReplies()) {
$reply_to = $this->getPublicReplyHandlerEmailAddress();
$mail->setReplyTo($reply_to);
}
$result[] = $mail;
return $result;
}
}
$tos = mpull($to_handles, null, 'getPHID');
$ccs = mpull($cc_handles, null, 'getPHID');
// Merge all the recipients together. TODO: We could keep the CCs as real
// CCs and send to a "noreply@domain.com" type address, but keep it simple
// for now.
$recipients = $tos + $ccs;
// When multiplexing mail, explicitly include To/Cc information in the
// message body and headers.
$mail_template = clone $mail_template;
$mail_template->addPHIDHeaders('X-Phabricator-To', array_keys($tos));
$mail_template->addPHIDHeaders('X-Phabricator-Cc', array_keys($ccs));
$body = $mail_template->getBody();
$body .= "\n";
$body .= $this->getRecipientsSummary($to_handles, $cc_handles);
foreach ($recipients as $phid => $recipient) {
$mail = clone $mail_template;
if (isset($to_handles[$phid])) {
$mail->addTos(array($phid));
} else if (isset($cc_handles[$phid])) {
$mail->addCCs(array($phid));
} else {
// not good - they should be a to or a cc
continue;
}
$mail->setBody($body);
$reply_to = null;
if (!$reply_to && $this->supportsPrivateReplies()) {
$reply_to = $this->getPrivateReplyHandlerEmailAddress($recipient);
}
if (!$reply_to && $this->supportsPublicReplies()) {
$reply_to = $this->getPublicReplyHandlerEmailAddress();
}
if ($reply_to) {
$mail->setReplyTo($reply_to);
}
$result[] = $mail;
}
return $result;
}
protected function getDefaultPublicReplyHandlerEmailAddress($prefix) {
$receiver = $this->getMailReceiver();
$receiver_id = $receiver->getID();
$domain = $this->getReplyHandlerDomain();
// We compute a hash using the object's own PHID to prevent an attacker
// from blindly interacting with objects that they haven't ever received
// mail about by just sending to D1@, D2@, etc...
- $hash = PhabricatorMetaMTAReceivedMail::computeMailHash(
+ $hash = PhabricatorObjectMailReceiver::computeMailHash(
$receiver->getMailKey(),
$receiver->getPHID());
$address = "{$prefix}{$receiver_id}+public+{$hash}@{$domain}";
return $this->getSingleReplyHandlerPrefix($address);
}
protected function getSingleReplyHandlerPrefix($address) {
$single_handle_prefix = PhabricatorEnv::getEnvConfig(
'metamta.single-reply-handler-prefix');
return ($single_handle_prefix)
? $single_handle_prefix . '+' . $address
: $address;
}
protected function getDefaultPrivateReplyHandlerEmailAddress(
PhabricatorObjectHandle $handle,
$prefix) {
if ($handle->getType() != PhabricatorPHIDConstants::PHID_TYPE_USER) {
// You must be a real user to get a private reply handler address.
return null;
}
$user = head(id(new PhabricatorPeopleQuery())
->withPhids(array($handle->getPHID()))
->execute());
$receiver = $this->getMailReceiver();
$receiver_id = $receiver->getID();
$user_id = $user->getID();
- $hash = PhabricatorMetaMTAReceivedMail::computeMailHash(
+ $hash = PhabricatorObjectMailReceiver::computeMailHash(
$receiver->getMailKey(),
$handle->getPHID());
$domain = $this->getReplyHandlerDomain();
$address = "{$prefix}{$receiver_id}+{$user_id}+{$hash}@{$domain}";
return $this->getSingleReplyHandlerPrefix($address);
}
final protected function enhanceBodyWithAttachments(
$body,
array $attachments,
$format = '- {F%d, layout=link}') {
if (!$attachments) {
return $body;
}
$files = id(new PhabricatorFile())
->loadAllWhere('phid in (%Ls)', $attachments);
// if we have some text then double return before adding our file list
if ($body) {
$body .= "\n\n";
}
foreach ($files as $file) {
$file_str = sprintf($format, $file->getID());
$body .= $file_str."\n";
}
return rtrim($body);
}
}
diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
index dd55adf4a9..ff1ec8f631 100644
--- a/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
+++ b/src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
@@ -1,566 +1,561 @@
array(
'headers' => self::SERIALIZATION_JSON,
'bodies' => self::SERIALIZATION_JSON,
'attachments' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function setHeaders(array $headers) {
// Normalize headers to lowercase.
$normalized = array();
foreach ($headers as $name => $value) {
$name = $this->normalizeMailHeaderName($name);
if ($name == 'message-id') {
$this->setMessageIDHash(PhabricatorHash::digestForIndex($value));
}
$normalized[$name] = $value;
}
$this->headers = $normalized;
return $this;
}
public function getHeader($key, $default = null) {
$key = $this->normalizeMailHeaderName($key);
return idx($this->headers, $key, $default);
}
private function normalizeMailHeaderName($name) {
return strtolower($name);
}
public function getMessageID() {
return $this->getHeader('Message-ID');
}
public function getSubject() {
return $this->getHeader('Subject');
}
public function getCCAddresses() {
return $this->getRawEmailAddresses(idx($this->headers, 'cc'));
}
public function getToAddresses() {
return $this->getRawEmailAddresses(idx($this->headers, 'to'));
}
private function loadExcludeMailRecipientPHIDs() {
$addresses = array_merge(
$this->getToAddresses(),
$this->getCCAddresses());
return $this->loadPHIDsFromAddresses($addresses);
}
final public function loadCCPHIDs() {
return $this->loadPHIDsFromAddresses($this->getCCAddresses());
}
private function loadPHIDsFromAddresses(array $addresses) {
if (empty($addresses)) {
return array();
}
$users = id(new PhabricatorUserEmail())
->loadAllWhere('address IN (%Ls)', $addresses);
$user_phids = mpull($users, 'getUserPHID');
$mailing_lists = id(new PhabricatorMetaMTAMailingList())
->loadAllWhere('email in (%Ls)', $addresses);
$mailing_list_phids = mpull($mailing_lists, 'getPHID');
return array_merge($user_phids, $mailing_list_phids);
}
/**
* Parses "to" addresses, looking for a public create email address
* first and if not found parsing the "to" address for reply handler
* information: receiver name, user id, and hash. If nothing can be
* found, it then loads user phids for as many to: email addresses as
* it can, theoretically falling back to create a conpherence amongst
* those users.
*/
private function getPhabricatorToInformation() {
// Only one "public" create address so far
$create_task = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.public-create-email');
// For replies, look for an object address with a format like:
// D291+291+b0a41ca848d66dcc@example.com
$single_handle_prefix = PhabricatorEnv::getEnvConfig(
'metamta.single-reply-handler-prefix');
$prefixPattern = ($single_handle_prefix)
? preg_quote($single_handle_prefix, '/') . '\+'
: '';
$pattern = "/^{$prefixPattern}((?:D|T|C|E)\d+)\+([\w]+)\+([a-f0-9]{16})@/U";
$phabricator_address = null;
$receiver_name = null;
$user_id = null;
$hash = null;
$user_phids = array();
$user_names = array();
foreach ($this->getToAddresses() as $address) {
if ($address == $create_task) {
$phabricator_address = $address;
// it's okay to stop here because we just need to map a create
// address to an application and don't need / won't have more
// information in these cases.
break;
}
$matches = null;
$ok = preg_match(
$pattern,
$address,
$matches);
if ($ok) {
$phabricator_address = $address;
$receiver_name = $matches[1];
$user_id = $matches[2];
$hash = $matches[3];
break;
}
$parts = explode('@', $address);
$maybe_name = trim($parts[0]);
$maybe_domain = trim($parts[1]);
$mail_domain = PhabricatorEnv::getEnvConfig('metamta.domain');
if ($mail_domain == $maybe_domain &&
PhabricatorUser::validateUsername($maybe_name)) {
$user_names[] = $maybe_name;
}
}
// since we haven't found a phabricator address, maybe this is
// someone trying to create a conpherence?
if (!$phabricator_address && $user_names) {
$users = id(new PhabricatorUser())
->loadAllWhere('userName IN (%Ls)', $user_names);
$user_phids = mpull($users, 'getPHID');
}
return array(
$phabricator_address,
$receiver_name,
$user_id,
$hash,
$user_phids
);
}
public function processReceivedMail() {
try {
$this->dropMailFromPhabricator();
$this->dropMailAlreadyReceived();
$receiver = $this->loadReceiver();
$sender = $receiver->loadSender($this);
} catch (PhabricatorMetaMTAReceivedMailProcessingException $ex) {
$this
->setStatus($ex->getStatusCode())
->setMessage($ex->getMessage())
->save();
return $this;
}
list($to,
$receiver_name,
$user_id,
$hash,
$user_phids) = $this->getPhabricatorToInformation();
if (!$to && !$user_phids) {
$raw_to = idx($this->headers, 'to');
return $this->setMessage("Unrecognized 'to' format: {$raw_to}")->save();
}
$from = idx($this->headers, 'from');
// TODO -- make this a switch statement / better if / when we add more
// public create email addresses!
$create_task = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.public-create-email');
if ($create_task && $to == $create_task) {
$receiver = new ManiphestTask();
$user = $this->lookupSender();
if ($user) {
$this->setAuthorPHID($user->getPHID());
} else {
$allow_email_users = PhabricatorEnv::getEnvConfig(
'phabricator.allow-email-users');
if ($allow_email_users) {
$email = new PhutilEmailAddress($from);
$xuser = id(new PhabricatorExternalAccount())->loadOneWhere(
'accountType = %s AND accountDomain IS NULL and accountID = %s',
'email',
$email->getAddress());
if (!$xuser) {
$xuser = new PhabricatorExternalAccount();
$xuser->setAccountID($email->getAddress());
$xuser->setAccountType('email');
$xuser->setDisplayName($email->getDisplayName());
$xuser->save();
}
$user = $xuser->getPhabricatorUser();
} else {
$default_author = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.default-public-author');
if ($default_author) {
$user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$default_author);
if (!$user) {
throw new Exception(
"Phabricator is misconfigured, the configuration key ".
"'metamta.maniphest.default-public-author' is set to user ".
"'{$default_author}' but that user does not exist.");
}
} else {
// TODO: We should probably bounce these since from the user's
// perspective their email vanishes into a black hole.
return $this->setMessage("Invalid public user '{$from}'.")->save();
}
}
}
$receiver->setAuthorPHID($user->getPHID());
$receiver->setOriginalEmailSource($from);
$receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$editor = new ManiphestTransactionEditor();
$editor->setActor($user);
$handler = $editor->buildReplyHandler($receiver);
$handler->setActor($user);
$handler->setExcludeMailRecipientPHIDs(
$this->loadExcludeMailRecipientPHIDs());
$handler->processEmail($this);
$this->setRelatedPHID($receiver->getPHID());
$this->setMessage('OK');
return $this->save();
}
// means we're creating a conpherence...!
if ($user_phids) {
// we must have a valid user who created this conpherence
$user = $this->lookupSender();
if (!$user) {
return $this->setMessage("Invalid public user '{$from}'.")->save();
}
$conpherence = id(new ConpherenceReplyHandler())
->setMailReceiver(new ConpherenceThread())
->setMailAddedParticipantPHIDs($user_phids)
->setActor($user)
->setExcludeMailRecipientPHIDs($this->loadExcludeMailRecipientPHIDs())
->processEmail($this);
$this->setRelatedPHID($conpherence->getPHID());
$this->setMessage('OK');
return $this->save();
}
if ($user_id == 'public') {
if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) {
return $this->setMessage("Public replies not enabled.")->save();
}
$user = $this->lookupSender();
if (!$user) {
return $this->setMessage("Invalid public user '{$from}'.")->save();
}
$use_user_hash = false;
} else {
$user = id(new PhabricatorUser())->load($user_id);
if (!$user) {
return $this->setMessage("Invalid private user '{$user_id}'.")->save();
}
$use_user_hash = true;
}
if ($user->getIsDisabled()) {
return $this->setMessage("User '{$user_id}' is disabled")->save();
}
$this->setAuthorPHID($user->getPHID());
$receiver = self::loadReceiverObject($receiver_name);
if (!$receiver) {
return $this->setMessage("Invalid object '{$receiver_name}'")->save();
}
$this->setRelatedPHID($receiver->getPHID());
if ($use_user_hash) {
// This is a private reply-to address, check that the user hash is
// correct.
$check_phid = $user->getPHID();
} else {
// This is a public reply-to address, check that the object hash is
// correct.
$check_phid = $receiver->getPHID();
}
- $expect_hash = self::computeMailHash($receiver->getMailKey(), $check_phid);
+ $expect_hash = PhabricatorObjectMailReceiver::computeMailHash(
+ $receiver->getMailKey(),
+ $check_phid);
if ($expect_hash != $hash) {
return $this->setMessage("Invalid mail hash!")->save();
}
if ($receiver instanceof ManiphestTask) {
$editor = new ManiphestTransactionEditor();
$editor->setActor($user);
$handler = $editor->buildReplyHandler($receiver);
} else if ($receiver instanceof DifferentialRevision) {
$handler = DifferentialMail::newReplyHandlerForRevision($receiver);
} else if ($receiver instanceof PhabricatorRepositoryCommit) {
$handler = PhabricatorAuditCommentEditor::newReplyHandlerForCommit(
$receiver);
} else if ($receiver instanceof ConpherenceThread) {
$handler = id(new ConpherenceReplyHandler())
->setMailReceiver($receiver);
}
$handler->setActor($user);
$handler->setExcludeMailRecipientPHIDs(
$this->loadExcludeMailRecipientPHIDs());
$handler->processEmail($this);
$this->setMessage('OK');
return $this->save();
}
public function getCleanTextBody() {
$body = idx($this->bodies, 'text');
$parser = new PhabricatorMetaMTAEmailBodyParser();
return $parser->stripTextBody($body);
}
public function getRawTextBody() {
return idx($this->bodies, 'text');
}
public static function loadReceiverObject($receiver_name) {
if (!$receiver_name) {
return null;
}
$receiver_type = $receiver_name[0];
$receiver_id = substr($receiver_name, 1);
$class_obj = null;
switch ($receiver_type) {
case 'T':
$class_obj = new ManiphestTask();
break;
case 'D':
$class_obj = new DifferentialRevision();
break;
case 'C':
$class_obj = new PhabricatorRepositoryCommit();
break;
case 'E':
$class_obj = new ConpherenceThread();
break;
default:
return null;
}
return $class_obj->load($receiver_id);
}
- public static function computeMailHash($mail_key, $phid) {
- $global_mail_key = PhabricatorEnv::getEnvConfig('phabricator.mail-key');
-
- $hash = PhabricatorHash::digest($mail_key.$global_mail_key.$phid);
- return substr($hash, 0, 16);
- }
-
/**
* Strip an email address down to the actual user@domain.tld part if
* necessary, since sometimes it will have formatting like
* '"Abraham Lincoln" '.
*/
private function getRawEmailAddress($address) {
$matches = null;
$ok = preg_match('/<(.*)>/', $address, $matches);
if ($ok) {
$address = $matches[1];
}
return $address;
}
private function getRawEmailAddresses($addresses) {
$raw_addresses = array();
foreach (explode(',', $addresses) as $address) {
$raw_addresses[] = $this->getRawEmailAddress($address);
}
return array_filter($raw_addresses);
}
private function lookupSender() {
$from = idx($this->headers, 'from');
$from = $this->getRawEmailAddress($from);
$user = PhabricatorUser::loadOneWithEmailAddress($from);
// If Phabricator is configured to allow "Reply-To" authentication, try
// the "Reply-To" address if we failed to match the "From" address.
$config_key = 'metamta.insecure-auth-with-reply-to';
$allow_reply_to = PhabricatorEnv::getEnvConfig($config_key);
if (!$user && $allow_reply_to) {
$reply_to = idx($this->headers, 'reply-to');
$reply_to = $this->getRawEmailAddress($reply_to);
if ($reply_to) {
$user = PhabricatorUser::loadOneWithEmailAddress($reply_to);
}
}
$allow_email_users = PhabricatorEnv::getEnvConfig(
'phabricator.allow-email-users');
if (!$user && $allow_email_users) {
$xusr = id(new PhabricatorExternalAccount())->loadOneWhere(
'accountType = %s AND accountDomain IS NULL and accountID = %s',
'email', $from);
$user = $xusr->getPhabricatorUser();
}
return $user;
}
/**
* If Phabricator sent the mail, always drop it immediately. This prevents
* loops where, e.g., the public bug address is also a user email address
* and creating a bug sends them an email, which loops.
*/
private function dropMailFromPhabricator() {
if (!$this->getHeader('x-phabricator-sent-this-message')) {
return;
}
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_FROM_PHABRICATOR,
"Ignoring email with 'X-Phabricator-Sent-This-Message' header to avoid ".
"loops.");
}
/**
* If this mail has the same message ID as some other mail, and isn't the
* first mail we we received with that message ID, we drop it as a duplicate.
*/
private function dropMailAlreadyReceived() {
$message_id_hash = $this->getMessageIDHash();
if (!$message_id_hash) {
// No message ID hash, so we can't detect duplicates. This should only
// happen with very old messages.
return;
}
$messages = $this->loadAllWhere(
'messageIDHash = %s ORDER BY id ASC LIMIT 2',
$message_id_hash);
$messages_count = count($messages);
if ($messages_count <= 1) {
// If we only have one copy of this message, we're good to process it.
return;
}
$first_message = reset($messages);
if ($first_message->getID() == $this->getID()) {
// If this is the first copy of the message, it is okay to process it.
// We may not have been able to to process it immediately when we received
// it, and could may have received several copies without processing any
// yet.
return;
}
$message = sprintf(
'Ignoring email with message id hash "%s" that has been seen %d '.
'times, including this message.',
$message_id_hash,
$messages_count);
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_DUPLICATE,
$message);
}
/**
* Load a concrete instance of the @{class:PhabricatorMailReceiver} which
* accepts this mail, if one exists.
*/
private function loadReceiver() {
$receivers = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorMailReceiver')
->loadObjects();
$accept = array();
foreach ($receivers as $key => $receiver) {
if (!$receiver->isEnabled()) {
continue;
}
if ($receiver->canAcceptMail($this)) {
$accept[$key] = $receiver;
}
}
if (!$accept) {
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_NO_RECEIVERS,
"No concrete, enabled subclasses of `PhabricatorMailReceiver` can ".
"accept this mail.");
}
if (count($accept) > 1) {
$names = implode(', ', array_keys($accept));
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_ABUNDANT_RECEIVERS,
"More than one `PhabricatorMailReceiver` claims to accept this mail.");
}
return head($accept);
}
}