loadObjectFromMail($mail, $sender); $mail->setRelatedPHID($object->getPHID()); $this->processReceivedObjectMail($mail, $object, $sender); return $this; } abstract protected function processReceivedObjectMail( PhabricatorMetaMTAReceivedMail $mail, PhabricatorLiskDAO $object, PhabricatorUser $sender); public function loadMailReceiverObject($pattern, PhabricatorUser $viewer) { return $this->loadObject($pattern, $viewer); } public function validateSender( PhabricatorMetaMTAReceivedMail $mail, PhabricatorUser $sender) { parent::validateSender($mail, $sender); $parts = $this->matchObjectAddressInMail($mail); try { $object = $this->loadObjectFromMail($mail, $sender); } catch (PhabricatorPolicyException $policy_exception) { throw new PhabricatorMetaMTAReceivedMailProcessingException( MetaMTAReceivedMailStatus::STATUS_POLICY_PROBLEM, pht( "This mail is addressed to an object you are not permitted ". "to see: %s", $policy_exception->getMessage())); } if (!$object) { throw new PhabricatorMetaMTAReceivedMailProcessingException( MetaMTAReceivedMailStatus::STATUS_NO_SUCH_OBJECT, pht( "This mail is addressed to an object ('%s'), but that object ". "does not exist.", $parts['pattern'])); } $sender_identifier = $parts['sender']; if ($sender_identifier === 'public') { if (!PhabricatorEnv::getEnvConfig('metamta.public-replies')) { throw new PhabricatorMetaMTAReceivedMailProcessingException( MetaMTAReceivedMailStatus::STATUS_NO_PUBLIC_MAIL, pht( "This mail is addressed to an object's public address, but ". "public replies are not enabled (`metamta.public-replies`).")); } $check_phid = $object->getPHID(); } else { if ($sender_identifier != $sender->getID()) { throw new PhabricatorMetaMTAReceivedMailProcessingException( MetaMTAReceivedMailStatus::STATUS_USER_MISMATCH, pht( "This mail is addressed to an object's private address, but ". "the sending user and the private address owner are not the ". "same user.")); } $check_phid = $sender->getPHID(); } $expect_hash = self::computeMailHash($object->getMailKey(), $check_phid); if ($expect_hash != $parts['hash']) { throw new PhabricatorMetaMTAReceivedMailProcessingException( MetaMTAReceivedMailStatus::STATUS_HASH_MISMATCH, pht( "The hash in this object's address does not match the expected ". "value.")); } } final public function canAcceptMail(PhabricatorMetaMTAReceivedMail $mail) { if ($this->matchObjectAddressInMail($mail)) { return true; } return false; } private function matchObjectAddressInMail( PhabricatorMetaMTAReceivedMail $mail) { foreach ($mail->getToAddresses() as $address) { $parts = $this->matchObjectAddress($address); if ($parts) { return $parts; } } return null; } private function matchObjectAddress($address) { $regexp = $this->getAddressRegexp(); $address = self::stripMailboxPrefix($address); $local = id(new PhutilEmailAddress($address))->getLocalPart(); $matches = null; if (!preg_match($regexp, $local, $matches)) { return false; } return $matches; } private function getAddressRegexp() { $pattern = $this->getObjectPattern(); $regexp = '(^'. '(?P'.$pattern.')'. '\\+'. '(?P\w+)'. '\\+'. '(?P[a-f0-9]{16})'. '$)Ui'; return $regexp; } private function loadObjectFromMail( PhabricatorMetaMTAReceivedMail $mail, PhabricatorUser $sender) { $parts = $this->matchObjectAddressInMail($mail); return $this->loadObject( phutil_utf8_strtoupper($parts['pattern']), $sender); } 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); } }