3 final class PhabricatorMetaMTAMailgunReceiveController
4 extends PhabricatorMetaMTAController
{
6 public function shouldRequireLogin() {
10 private function verifyMessage() {
11 $request = $this->getRequest();
12 $timestamp = $request->getStr('timestamp');
13 $token = $request->getStr('token');
14 $sig = $request->getStr('signature');
16 // An install may configure multiple Mailgun mailers, and we might receive
17 // inbound mail from any of them. Test the signature to see if it matches
18 // any configured Mailgun mailer.
20 $mailers = PhabricatorMetaMTAMail
::newMailers(
24 PhabricatorMailMailgunAdapter
::ADAPTERTYPE
,
27 foreach ($mailers as $mailer) {
28 $api_key = $mailer->getOption('api-key');
29 $hash = hash_hmac('sha256', $timestamp.$token, $api_key);
30 if (phutil_hashes_are_identical($sig, $hash)) {
38 public function handleRequest(AphrontRequest
$request) {
40 // No CSRF for Mailgun.
41 $unguarded = AphrontWriteGuard
::beginScopedUnguardedWrites();
43 if (!$this->verifyMessage()) {
45 pht('Mail signature is not valid. Check your Mailgun API key.'));
48 $raw_headers = $request->getStr('message-headers');
50 if (strlen($raw_headers)) {
51 $raw_headers = phutil_json_decode($raw_headers);
52 foreach ($raw_headers as $raw_header) {
53 list($name, $value) = $raw_header;
54 $raw_dict[$name] = $value;
59 'to' => $request->getStr('recipient'),
60 'from' => $request->getStr('from'),
61 'subject' => $request->getStr('subject'),
64 $received = new PhabricatorMetaMTAReceivedMail();
65 $received->setHeaders($headers);
66 $received->setBodies(array(
67 'text' => $request->getStr('stripped-text'),
68 'html' => $request->getStr('stripped-html'),
71 $file_phids = array();
72 foreach ($_FILES as $file_raw) {
74 $file = PhabricatorFile
::newFromPHPUpload(
77 'viewPolicy' => PhabricatorPolicies
::POLICY_NOONE
,
79 $file_phids[] = $file->getPHID();
80 } catch (Exception
$ex) {
84 $received->setAttachments($file_phids);
88 $received->processReceivedMail();
89 } catch (Exception
$ex) {
90 // We can get exceptions here in two cases.
92 // First, saving the message may throw if we have already received a
93 // message with the same Message ID. In this case, we're declining to
94 // process a duplicate message, so failing silently is correct.
96 // Second, processing the message may throw (for example, if it contains
97 // an invalid !command). This will generate an email as a side effect,
98 // so we don't need to explicitly handle the exception here.
100 // In these cases, we want to return HTTP 200. If we do not, MailGun will
101 // re-transmit the message later.
105 $response = new AphrontWebpageResponse();
106 $response->setContent(pht("Got it! Thanks, Mailgun!\n"));