01tonythomas has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/144656

Change subject: Added API to handle incoming emails automatically
......................................................................

Added API to handle incoming emails automatically

Exim should POST the bounce emails via curl using, the pipe config
command = /bin/curl curl -d "action=bouncehandler" --data-urlencode
"email@-" http://localhost/core/core/api.php
This will call the API and pass the argument email to it, so that
details can be extracted for processing.
Headers are extracted using preg_match.

Working verified in local setup.

Change-Id: I76fcd68df196170a16646e99095f7517dcb04e40
---
A ApiBounceHandler.php
M BounceHandler.php
2 files changed, 180 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/BounceHandler 
refs/changes/56/144656/1

diff --git a/ApiBounceHandler.php b/ApiBounceHandler.php
new file mode 100644
index 0000000..1d14383
--- /dev/null
+++ b/ApiBounceHandler.php
@@ -0,0 +1,172 @@
+<?php
+/**
+ * API to handle e-mail bounces
+ *
+ */
+class ApiBounceHandler extends ApiBase {
+       public function execute() {
+               $email = $this->getMain()->getVal( 'email' );
+               $emailHeaders = array();
+               $failedUser = array();
+
+               // Extract headers from raw email
+               $emailHeaders  = self::getHeaders( $email );
+               // Extract required header fields
+               $to = $emailHeaders[ 'to' ];
+               $subject = $emailHeaders[ 'subject' ];
+               $emailDate = $emailHeaders[ 'date' ];
+
+               // Get original failed user email and wiki details
+               $failedUser = self::getUserDetails( $to );
+               $wikiId = $failedUser[ 'wikiId' ];
+               $originalEmail = $failedUser[ 'rawEmail' ];
+
+               $bounceTimestamp = wfTimestamp( TS_MW, $emailDate );
+               $dbw = wfGetDB( DB_MASTER, array(), $wikiId );
+               if( is_array( $failedUser ) ) {
+                       $rowData = array(
+                               'br_user' => $originalEmail,
+                               'br_timestamp' => $bounceTimestamp,
+                               'br_reason' => $subject
+                       );
+                       $dbw->insert( 'bounce_records', $rowData, __METHOD__ );
+               }
+               self::BounceHandlerActions( $wikiId, $originalEmail, 
$bounceTimestamp );
+               return true;
+       }
+
+       /**
+        * Extract the required headers from the received email
+        *
+        * @param $email
+        * @return string
+        */
+       protected function getHeaders( $email ) {
+               $emailLines = explode( "\n", $email );
+               $to = " ";
+               for ( $i = 0; $i < count( $emailLines ); $i++ ) {
+                       if ( preg_match( "/^To: (.*)/", $emailLines[$i], 
$toMatch ) ) {
+                               $headers[ 'to' ] = $toMatch[1];
+                       }
+                       if ( preg_match( "/^Subject: (.*)/", $emailLines[$i], 
$subjectMatch ) ) {
+                               $headers[ 'subject' ] = $subjectMatch[1];
+                       }
+                       if ( preg_match( "/^Date: (.*)/", $emailLines[$i], 
$dateMatch ) ) {
+                               $headers[ 'date' ] = $dateMatch[1];
+                       }
+                       if ( trim( $emailLines[$i] ) == "" ) {
+                               // Empty line denotes that the header part is 
finished
+                               break;
+                       }
+               }
+
+               return $headers;
+       }
+
+       /**
+        * Validate and extract user info from a given VERP address and
+        *
+        * return the failed user details, if hashes match
+        * @param string $hashedEmail The original hashed Email from bounce 
email
+        * @return array $failedUser The failed user details
+        * */
+       protected function getUserDetails( $hashedEmail ) {
+               global $wgVERPalgorithm, $wgVERPsecret, $wgVERPAcceptTime;
+               $currentTime = wfTimestamp();
+               $failedUser = array();
+               preg_match( '~(.*?)@~', $hashedEmail, $hashedPart );
+               $hashedVERPPart = explode( '-', $hashedPart[1] );
+               $hashedData = $hashedVERPPart[0]. '-'. $hashedVERPPart[1]. '-'. 
$hashedVERPPart[2];
+               $emailTime = base_convert( $hashedVERPPart[2], 36, 10 );
+               if ( hash_hmac( $wgVERPalgorithm, $hashedData, $wgVERPsecret ) 
=== $hashedVERPPart[3] &&
+               $currentTime - $emailTime < $wgVERPAcceptTime ) {
+                       $failedUser[ 'wikiId' ] = str_replace( '.', '-', 
$hashedVERPPart[0] );
+                       $rawUserId = base_convert( $hashedVERPPart[1], 36, 10 );
+                       $failedUser[ 'rawEmail' ] = self::getOriginalEmail( 
$failedUser, $rawUserId );
+               } else {
+                       wfDebugLog( 'BounceHandler',
+                       "Error: Hash validation failed. Expected hash of 
$hashedData, got $hashedVERPPart[3]." );
+               }
+
+               return $failedUser;
+       }
+
+       /**
+        * Generate Original Email Id from a hashed emailId
+        *
+        * @param array $failedUser The failed user details
+        * @param string $rawUserId The userId of the failing recipient
+        * @return string $rawEmail The emailId of the failing recipient
+        */
+       protected function getOriginalEmail( $failedUser, $rawUserId ) {
+               // In multiple wiki deployed case, the $wikiId can help 
correctly identify the user after looking up in
+               // the required database.
+               $wikiId = $failedUser[ 'wikiId' ];
+               $dbr = wfGetDB( DB_SLAVE, array(), $wikiId );
+               $res = $dbr->selectRow(
+                       'user',
+                       array( 'user_email' ),
+                       array(
+                               'user_id' => $rawUserId,
+                       ),
+                       __METHOD__
+               );
+               if( $res !== false ) {
+                       $rawEmail = $res->user_email;
+               } else {
+                       wfDebugLog( 'BounceHandler',"Error fetching email_id of 
user_id $rawUserId from Database $wikiId." );
+               }
+
+               return $rawEmail;
+       }
+
+       /**
+        * Perform actions on users who failed to receive emails in a given 
period
+        *
+        * @param string $wikiId The database id of the failing recipient
+        * @param string $originalEmail The email-id of the failing recipient
+        * @param string $bounceTimestamp The bounce mail timestamp
+        * @return bool
+        */
+       protected static function BounceHandlerActions( $wikiId, 
$originalEmail, $bounceTimestamp ) {
+               global $wgBounceRecordPeriod, $wgBounceRecordLimit;
+               $unixTime = wfTimestamp();
+               $bounceValidPeriod = wfTimestamp( TS_MW, $unixTime - 
$wgBounceRecordPeriod );
+               $dbr = wfGetDB( DB_SLAVE, array(), $wikiId );
+               $res = $dbr->selectRow( 'bounce_records',
+                       array(
+                               'COUNT(*) as total_count'
+                       ),
+                       array(
+                               'br_user'=> $originalEmail
+                       ),
+                       __METHOD__
+               );
+               if( $res !== false ) {
+                       if ( $res->total_count > $wgBounceRecordLimit ) {
+                               //Un-subscribe the user
+                               $dbw = wfGetDB( DB_MASTER, array(), $wikiId );
+                               $res = $dbw->update( 'user',
+                                       array(
+                                               'user_email_authenticated' => 
null,
+                                               'user_email_token' => null,
+                                               'user_email_token_expires' => 
null
+                                       ),
+                                       array( 'user_email' => $originalEmail ),
+                                       __METHOD__
+                               );
+                               if ( $res ) {
+                                       wfDebugLog( 'BounceHandler', 
"Un-subscribed user $originalEmail for exceeding Bounce
+                                                       Limit 
$wgBounceRecordLimit" );
+                               } else {
+                                       wfDebugLog( 'BounceHandler', "Failed to 
un-subscribe the failing recipient $originalEmail" );
+                               }
+                       }
+               } else {
+                       wfDebugLog( 'BounceHandler',"Error fetching the count 
of past bounces for user $originalEmail" );
+               }
+
+               return true;
+       }
+
+}
\ No newline at end of file
diff --git a/BounceHandler.php b/BounceHandler.php
index 5e04e45..871fddf 100644
--- a/BounceHandler.php
+++ b/BounceHandler.php
@@ -21,6 +21,11 @@
 //Hooks files
 $wgAutoloadClasses['BounceHandlerHooks'] =  $dir. '/BounceHandlerHooks.php';
 
+//Register and Load BounceHandler API
+$wgAutoloadClasses['ApiBounceHandler'] = $dir. '/ApiBounceHandler.php';
+$wgAPIModules['bouncehandler'] = 'ApiBounceHandler';
+
+
 //Register Hooks
 $wgHooks['UserMailerChangeReturnPath'][] = 
'BounceHandlerHooks::onVERPAddressGenerate';
 
@@ -46,4 +51,6 @@
 /* IMAP configs */
 $wgIMAPuser = 'user';
 $wgIMAPpass = 'pass';
-$wgIMAPserver = '{localhost:143/imap/novalidate-cert}INBOX';
\ No newline at end of file
+$wgIMAPserver = '{localhost:143/imap/novalidate-cert}INBOX';
+
+return true;
\ No newline at end of file

-- 
To view, visit https://gerrit.wikimedia.org/r/144656
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I76fcd68df196170a16646e99095f7517dcb04e40
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/BounceHandler
Gerrit-Branch: master
Gerrit-Owner: 01tonythomas <01tonytho...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to