Example #1
0
        /// <summary>
        /// Attempts to find and process the reason an email bounced by digging through all body parts.
        /// </summary>
        /// <param name="bodyParts">Array of BodyParts to dig through; may include child body parts.</param>
        /// <param name="bouncePair">out.  A BouncePair object containing details of the bounce (if a reason was found).</param>
        /// <param name="bounceMessage">out.  The text to use as the reason found why the bounce occurred.</param>
        /// <returns>true if a reason was found, else false.</returns>
        internal bool FindBounceReason(BodyPart[] bodyParts, out BouncePair bouncePair, out string bounceMessage, out EmailProcessingDetails bounceIdentification)
        {
            foreach (BodyPart b in bodyParts)
            {
                if (b.ContentType.MediaType.StartsWith("multipart/", StringComparison.OrdinalIgnoreCase))
                {
                    // Just a container for other body parts so check them.
                    if (FindBounceReason(b.BodyParts, out bouncePair, out bounceMessage, out bounceIdentification))
                    {
                        return(true);
                    }
                }
                else if (b.ContentType.MediaType.Equals("text/plain", StringComparison.OrdinalIgnoreCase))
                {
                    // Only useful to examine text/plain body parts (so not "image/gif" etc).
                    if (ParseBounceMessage(b.GetDecodedBody(), out bouncePair, out bounceMessage, out bounceIdentification))
                    {
                        return(true);
                    }
                }
                // else
                //	Console.WriteLine("\tSkipped bodypart \"" + b.ContentType.MediaType + "\".");
            }


            // Still here so haven't found anything useful.
            bouncePair.BounceCode = MantaBounceCode.Unknown;
            bouncePair.BounceType = MantaBounceType.Unknown;
            bounceMessage         = string.Empty;

            bounceIdentification = new EmailProcessingDetails();
            bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce;

            return(false);
        }
Example #2
0
        /// <summary>
        /// Examines an SMTP response that is thought to relate to delivery of an email failing.
        /// </summary>
        /// <param name="message">An SMTP response either found as the "Diagnostic-Code" value in a Non-Delivery Report
        /// or received directly from another MTA in an SMTP session.</param>
        /// <param name="bouncePair">out.  Details of the Bounce (or not) based on details found in <paramref name="message"/>.</param>
        /// <param name="bounceMessage">out.  The message found that indicated a Bounce or string.Empty if it wasn't
        /// found to indicate a bounce.</param>
        /// <returns>true if a bounce was positively identified, else false.</returns>
        internal bool ParseSmtpDiagnosticCode(string message, out BouncePair bouncePair, out string bounceMessage, out EmailProcessingDetails bounceIdentification)
        {
            // Remove "smtp;[possible whitespace]" if it appears at the beginning of the message.
            if (message.StartsWith("smtp;", StringComparison.OrdinalIgnoreCase))
            {
                message = message.Substring("smtp;".Length).Trim();
            }

            if (ParseBounceMessage(message, out bouncePair, out bounceMessage, out bounceIdentification))
            {
                return(true);
            }


            bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce;

            return(false);
        }
Example #3
0
        /// <summary>
        /// Attempts to find the reason for the bounce by running Bounce Rules, then checking for Non-Delivery Report codes,
        /// and finally checking for SMTP codes.
        /// </summary>
        /// <param name="message">Could either be an email body part or a single or multiple line response from another MTA.</param>
        /// <param name="bouncePair">out.</param>
        /// <param name="bounceMessage">out.</param>
        /// <returns>true if a positive match in <paramref name="message"/> was found indicating a bounce, else false.</returns>
        internal bool ParseBounceMessage(string message, out BouncePair bouncePair, out string bounceMessage, out EmailProcessingDetails bounceIdentification)
        {
            bounceIdentification = new EmailProcessingDetails();



            // Check all Bounce Rules for a match.
            foreach (BounceRule r in BounceRulesManager.BounceRules)
            {
                // If we get a match, we're done processing Rules.
                if (r.IsMatch(message, out bounceMessage))
                {
                    bouncePair.BounceType = r.BounceTypeIndicated;
                    bouncePair.BounceCode = r.BounceCodeIndicated;
                    bounceMessage         = message;

                    bounceIdentification.BounceIdentifier     = Core.Enums.BounceIdentifier.BounceRule;
                    bounceIdentification.MatchingBounceRuleID = r.RuleID;
                    bounceIdentification.MatchingValue        = r.Criteria;

                    return(true);
                }
            }


            // No Bounce Rules match the message so try to get a match on an NDR code ("5.1.1") or an SMTP code ("550").
            // TODO: Handle several matches being found - somehow find The Best?
            // Pattern: Should match like this:
            //	[anything at the beginning if present][then either an SMTP code or an NDR code, but both should be grabbed if
            // they exist][then the rest of the content (if any)]
            Match match = Regex.Match(message, RegexPatterns.SmtpResponse, RegexOptions.Singleline | RegexOptions.ExplicitCapture);

            if (match.Success)
            {
                bounceMessage = match.Value;

                // Check for anything useful with the NDR code first as it contains more specific detail than the SMTP code.
                if (match.Groups["NdrCode"].Success && match.Groups["NdrCode"].Length > 0)
                {
                    bouncePair = BounceRulesManager.Instance.ConvertNdrCodeToMantaBouncePair(match.Groups["NdrCode"].Value);
                    if (bouncePair.BounceType != MantaBounceType.Unknown)
                    {
                        bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.NdrCode;
                        bounceIdentification.MatchingValue    = match.Groups["NdrCode"].Value;

                        return(true);
                    }
                }

                // Try the SMTP code as there wasn't an NDR.
                if (match.Groups["SmtpCode"].Success && match.Groups["SmtpCode"].Length > 0)
                {
                    bouncePair = BounceRulesManager.Instance.ConvertSmtpCodeToMantaBouncePair(Int32.Parse(match.Groups["SmtpCode"].Value));

                    bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.SmtpCode;
                    bounceIdentification.MatchingValue    = match.Groups["SmtpCode"].Value;

                    return(true);
                }
            }


            // Failed to identify a reason so shouldn't be a bounce.
            bouncePair.BounceCode = MantaBounceCode.Unknown;
            bouncePair.BounceType = MantaBounceType.Unknown;
            bounceMessage         = string.Empty;

            bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce;
            bounceIdentification.MatchingValue    = string.Empty;

            return(false);
        }
Example #4
0
        /// <summary>
        /// Examines an SMTP response message to identify detailed bounce information from it.
        /// </summary>
        /// <param name="response">The message that's come back from an external MTA when attempting to send an email.</param>
        /// <param name="rcptTo">The email address that was being sent to.</param>
        /// <param name="internalSendID">The internal Manta SendID.</param>
        /// <returns>True if a bounce was found and recorded, false if not.</returns>
        internal bool ProcessSmtpResponseMessage(string response, string rcptTo, int internalSendID, out EmailProcessingDetails bounceIdentification)
        {
            bounceIdentification = new EmailProcessingDetails();



            // Check for TimedOutInQueue message first.
            if (response.Equals(MtaParameters.TIMED_OUT_IN_QUEUE_MESSAGE, StringComparison.OrdinalIgnoreCase))
            {
                bounceIdentification = null;

                MantaTimedOutInQueueEvent timeOut = new MantaTimedOutInQueueEvent
                {
                    EventType    = MantaEventType.TimedOutInQueue,
                    EmailAddress = rcptTo,
                    SendID       = SendDB.GetSend(internalSendID).ID,
                    EventTime    = DateTime.UtcNow
                };

                // Log to DB.
                Save(timeOut);

                // All done return true.
                return(true);
            }



            BouncePair bouncePair    = new BouncePair();
            string     bounceMessage = string.Empty;


            if (ParseBounceMessage(response, out bouncePair, out bounceMessage, out bounceIdentification))
            {
                // Were able to find the bounce so create the bounce event.
                MantaBounceEvent bounceEvent = new MantaBounceEvent
                {
                    EventType    = MantaEventType.Bounce,
                    EmailAddress = rcptTo,
                    BounceInfo   = bouncePair,
                    SendID       = SendDB.GetSend(internalSendID).ID,
                    // It is possible that the bounce was generated a while back, but we're assuming "now" for the moment.
                    // Might be good to get the DateTime found in the email at a later point.
                    EventTime = DateTime.UtcNow,
                    Message   = response
                };

                // Log to DB.
                Save(bounceEvent);


                // All done return true.
                return(true);
            }



            // Couldn't identify the bounce.

            bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce;

            return(false);
        }
Example #5
0
        /// <summary>
        /// Examines a non-delivery report for detailed bounce information.
        /// </summary>
        /// <param name="message"></param>
        /// <param name="bounceType"></param>
        /// <param name="bounceCode"></param>
        /// <param name="bounceMessage"></param>
        /// <returns></returns>
        internal bool ParseNdr(string message, out BouncePair bouncePair, out string bounceMessage, out EmailProcessingDetails bounceIdentification)
        {
            bounceIdentification = new EmailProcessingDetails();


            // Check for the Diagnostic-Code as hopefully contains more information about the error.
            const string DiagnosticCodeFieldName = "Diagnostic-Code: ";
            const string StatusFieldName         = "Status: ";



            StringBuilder diagnosticCode = new StringBuilder(string.Empty);
            string        status         = string.Empty;

            using (StringReader sr = new StringReader(message))
            {
                string line = sr.ReadToCrLf();

                // While the string reader has stuff to read keep looping through each line.
                while (!string.IsNullOrWhiteSpace(line) || sr.Peek() > -1)
                {
                    if (line.StartsWith(DiagnosticCodeFieldName, StringComparison.OrdinalIgnoreCase))
                    {
                        // Found the diagnostic code.

                        // Remove the field name.
                        line = line.Substring(DiagnosticCodeFieldName.Length);

                        // Check to see if the disagnostic-code contains an SMTP response.
                        bool isSmtpResponse = line.StartsWith("smtp;", StringComparison.OrdinalIgnoreCase);

                        // Add the first line of the diagnostic-code.
                        diagnosticCode.AppendLine(line);

                        // Will be set to true when we find the next non diagnostic-code line.
                        bool foundNextLine = false;

                        // Loop to read multiline diagnostic-code.
                        while (!foundNextLine)
                        {
                            if (sr.Peek() == -1)
                            {
                                break;                                 // We've reached the end of the string!
                            }
                            // Read the next line.
                            line = sr.ReadToCrLf();

                            if (isSmtpResponse)
                            {
                                // Diagnostic code is an SMTP response so look for SMTP response line.
                                if (Regex.IsMatch(line, @"\d{3}(-|\s)"))
                                {
                                    diagnosticCode.AppendLine(line);
                                }
                                else                                 // Not a SMTP response line so must be next NDR line.
                                {
                                    foundNextLine = true;
                                }
                            }
                            else
                            {
                                // Non SMTP response. If first char is whitespace then it's part of the disagnostic-code otherwise it isn't.
                                if (char.IsWhiteSpace(line[0]))
                                {
                                    diagnosticCode.AppendLine(line);
                                }
                                else
                                {
                                    foundNextLine = true;
                                }
                            }
                        }
                    }
                    else
                    {
                        // We haven't found a diagnostic-code line.
                        // Check to see if we have found a status field.
                        if (line.StartsWith(StatusFieldName, StringComparison.OrdinalIgnoreCase))
                        {
                            status = line.Substring(StatusFieldName.Length).TrimEnd();
                        }

                        // If there is more of the string to read then read the next line, otherwise set line to string.empty.
                        if (sr.Peek() > -1)
                        {
                            line = sr.ReadToCrLf();
                        }
                        else
                        {
                            line = string.Empty;
                        }
                    }
                }
            }


            // Process what we've managed to find...

            // Diagnostic-Code
            if (!string.IsNullOrWhiteSpace(diagnosticCode.ToString()))
            {
                if (ParseSmtpDiagnosticCode(diagnosticCode.ToString(), out bouncePair, out bounceMessage, out bounceIdentification))
                {
                    return(true);
                }
            }


            // Status
            if (!string.IsNullOrWhiteSpace(status))
            {
                // If there's an NDR code in the Status value, use it.
                Match m = Regex.Match(status, RegexPatterns.NonDeliveryReportCode, RegexOptions.ExplicitCapture);

                if (m.Success)
                {
                    bouncePair    = BounceRulesManager.Instance.ConvertNdrCodeToMantaBouncePair(m.Value);
                    bounceMessage = m.Value;

                    bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.NdrCode;
                    bounceIdentification.MatchingValue    = m.Value;
                    return(true);
                }
            }



            // If we've not already returned from this method, then we're still looking for an explanation
            // for the bounce so parse the entire message as a string.
            if (ParseBounceMessage(message, out bouncePair, out bounceMessage, out bounceIdentification))
            {
                return(true);
            }



            // Nope - no clues relating to why the bounce occurred.
            bouncePair.BounceType = MantaBounceType.Unknown;
            bouncePair.BounceCode = MantaBounceCode.Unknown;
            bounceMessage         = string.Empty;

            bounceIdentification.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce;
            bounceIdentification.MatchingValue    = string.Empty;

            return(false);
        }