@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PhabricatorMetaMTAMailgunReceiveController
4 extends PhabricatorMetaMTAController {
5
6 public function shouldRequireLogin() {
7 return false;
8 }
9
10 private function verifyMessage() {
11 $request = $this->getRequest();
12 $timestamp = $request->getStr('timestamp');
13 $token = $request->getStr('token');
14 $sig = $request->getStr('signature');
15
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.
19
20 $mailers = PhabricatorMetaMTAMail::newMailers(
21 array(
22 'inbound' => true,
23 'types' => array(
24 PhabricatorMailMailgunAdapter::ADAPTERTYPE,
25 ),
26 ));
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)) {
31 return true;
32 }
33 }
34
35 return false;
36 }
37
38 public function handleRequest(AphrontRequest $request) {
39
40 // No CSRF for Mailgun.
41 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
42
43 if (!$this->verifyMessage()) {
44 throw new Exception(
45 pht('Mail signature is not valid. Check your Mailgun API key.'));
46 }
47
48 $raw_headers = $request->getStr('message-headers');
49 $raw_dict = array();
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;
55 }
56 }
57
58 $headers = array(
59 'to' => $request->getStr('recipient'),
60 'from' => $request->getStr('from'),
61 'subject' => $request->getStr('subject'),
62 ) + $raw_dict;
63
64 $received = new PhabricatorMetaMTAReceivedMail();
65 $received->setHeaders($headers);
66 $received->setBodies(array(
67 'text' => $request->getStr('stripped-text'),
68 'html' => $request->getStr('stripped-html'),
69 ));
70
71 $file_phids = array();
72 foreach ($_FILES as $file_raw) {
73 try {
74 $file = PhabricatorFile::newFromPHPUpload(
75 $file_raw,
76 array(
77 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
78 ));
79 $file_phids[] = $file->getPHID();
80 } catch (Exception $ex) {
81 phlog($ex);
82 }
83 }
84 $received->setAttachments($file_phids);
85
86 try {
87 $received->save();
88 $received->processReceivedMail();
89 } catch (Exception $ex) {
90 // We can get exceptions here in two cases.
91
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.
95
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.
99
100 // In these cases, we want to return HTTP 200. If we do not, MailGun will
101 // re-transmit the message later.
102 phlog($ex);
103 }
104
105 $response = new AphrontWebpageResponse();
106 $response->setContent(pht("Got it! Thanks, Mailgun!\n"));
107 return $response;
108 }
109
110}