Thursday, October 22, 2009

Preventing Forwarding NDRs Storms

One recurring problem I have is users setting up forwarding rules to e-mail addresses that don’t exist (why do they never check it?!).
Anyway, when they receive an e-mail, that e-mail gets forwarded automatically to that non-existing address which generates a Non-Delivery Receipt (NDR). When the user receives the NDR, it gets forwarded to the same address, which creates another NDR, and so on, and so on, and so on, going into an infinite loop. This is known as an NDR Storm.


What is an NDR?
An NDR is a message that a mail server sends to notify the sender when a problem occurs with the delivery of the e-mail. For example if the server resources are unavailable, the recipient's mailbox is full (and it doesn’t accepts more e-mails), the recipient is unknown, etc.

In my case, the users always type the recipient's address incorrectly... So, the receiving server sends a message that looks similar to this:

-------------------------------------------
From: mailer-daemon
Sent: Wednesday, October 21, 2009 11:06:38 PM
To: Nuno Mota
Subject: There was an error sending your mail ...
Auto forwarded by a Rule

I'm afraid I had problems forwarding your message. Full details follow:

Subject: 'just testing the forwarding rule'
Date: 'Wed, 21 Oct 2009 23:06:38 +0100 (BST)'

1 error(s):

SMTP Server rejected recipient (Error following RCPT command). It responded as follows: [550 unknown user xxxx.xxxx]

I have also attached the mail's original headers. Sorry it didn't work out.



First, I created a transport rule to BCC every e-mail sent to outside users and that contained the text "FW: There was an error sending your mail", "FW: Mail delivery failed", "FW: failure notice" in the subject to our Quarantine mailbox. This way, every time I got loads of NDRs I just had to go to the user’s mailbox and disable the rule (if that was the case of course).

This was working fine, except for the times this happened during the night. When I got to work, sometimes I had users with 2GB of NDRs in just a few hours...

So, another approach was necessary. That’s when I thought of writing a script to analyze the transport logs every hour (changed it to 15 minutes for now) and, in case an NDR Storm was detected, create a Transport Rule to block those e-mails from going out.

And here is the result:

# Script: PreventNDRsStorm.ps1
# Purpose: Analyze the Transport Logs for possible NDR storms and stop them
# Author: Nuno Mota
# Date: Oct 2009


# Get the date and time for 15 minutes ago. This will be the starting point to search the transport logs

$strDate = Get-Date
$strStartFrom = $strDate - "00:15:00"

# Get all the users who forwarded non-delivered messages to external users in the last 15 minutes. Group them so we can analyze the total number of e-mails sent
$strNDRs = Get-TransportServer Get-MessageTrackingLog -ResultSize Unlimited -Start $strStartFrom -EventId SEND ? {($_.MessageSubject -match "FW: There was an error sending your mail") -or ($_.MessageSubject -match "FW: Mail delivery failed") -or ($_.MessageSubject -match "FW: failure notice")} ¦ Group Sender

# For each sender, check if they sent more than 25 e-mails
ForEach ($strNDR in $strNDRs)
{
# If they sent more than 25 e-mails (in the last 15 minutes) create the transport rule and send an e-mail to the administrator
If (($strNDR.Count -ge 25) -and ($strNDR.Name -match "@letsexchange.com "))
{
# Create the Transport Rule
# For every e-mail sent by that user

$condition1 = Get-TransportRulePredicate From
$condition1.Addresses = @(Get-Mailbox $strNDR.Name)

# only when the e-mail is going Outside the organization
$condition2 = Get-TransportRulePredicate SentToScope
$condition2.Scope = @("NotInOrganization")

# and only when the subject contains any of these phrases
$condition3 = Get-TransportRulePredicate SubjectContains
$condition3.Words = @("FW: There was an error sending your mail", "FW: Mail delivery failed", "FW: failure notice")

# Redirect the FW e-mail to the Quarantine NDRs mailbox
$action = Get-TransportRuleAction RedirectMessage
$action.Addresses = @(Get-Mailbox Quarantine)

# Get the user's alias from the e-mail address to create the transport rule with it
$strName = [regex]::split($strNDR.Name, "@")[0]

# Create the Transport Rule itself
New-TransportRule -Name "Prevent NDRs Storm - $strName" -Comments "Prevent NDRs Storm by blocking specific sender after searching the Transport Logs for more than 25 e-mails forwarded by one single user" -Conditions @($condition1, $condition2, $condition3) -Actions @($action) -Enabled $True -Priority 0


# Send the E-mail to the administrator
$body = ""
$body += "`n**********************************"
$body += "`n* *"
$body += "`n* WARNING: NDR Storm Prevented *"
$body += "`n* *"
$body += "`n**********************************"
$body += "`n`nTransport Rule Created for ", $strNDR.Name
$body += "`n`nFW e-mails: ", $strNDR.Count

$FromAddress = "administrator@arts.ac.uk"
$ToAddress = "nuno.mota@letsexchange.com"
$MessageSubject = "NDRs Storm!"

$SendingServer = "HTCAS2"

$SMTPMessage = New-Object System.Net.Mail.MailMessage $FromAddress, $ToAddress, $MessageSubject, $body

$SMTPClient = New-Object System.Net.Mail.SMTPClient $SendingServer
$SMTPClient.Send($SMTPMessage)
}
}



(My apologies for the bad layout of the code...)

What it does is analyze the transport logs every 15 minutes and if it finds a user that forwarded 25 or more NDRs, creates a transport just for that user rule and block those e-mails from going out by redirecting them to the Quarantine mailbox.
Then, I receive an e-mail saying a transport rule was created and all I have to do is go to the user’s mailbox, disable the rule and delete the transport rule after a few minutes.


Job done! Any other ideas?

2 comments:

  1. Hello Nuno, great article! Do you know if this script is also valid for Exchange 2003? Thank you in advanced!

    ReplyDelete
    Replies
    1. Hi,
      Thank you! :) Unfortunately no... This is a PowerShell script, which Exchange 2003 does not support. PowerShell is only available in Exchange 2007 and above...

      Delete