Exemple #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);
        }
Exemple #2
0
        /// <summary>
        /// Compares two EmailProcessingDetails objects to see if they are equal.
        /// </summary>
        /// <param name="obj">Another EmailProcessingDetails object to compare to this one.</param>
        /// <returns>true if the two objects have the same value, else false.</returns>
        public override bool Equals(object obj)
        {
            if (!(obj is EmailProcessingDetails))
            {
                return(false);
            }


            EmailProcessingDetails otherObj = obj as EmailProcessingDetails;

            if (this.ProcessingResult == otherObj.ProcessingResult && this.BounceIdentifier == otherObj.BounceIdentifier && this.MatchingBounceRuleID == otherObj.MatchingBounceRuleID && this.MatchingValue == otherObj.MatchingValue)
            {
                return(true);
            }
            else
            {
                return(false);
            }
        }
Exemple #3
0
        /// <summary>
        /// Looks through a feedback loop email looking for something to identify it as an abuse report and who it relates to.
        /// If found, logs the event.
        ///
        /// How to get the info depending on the ESP (and this is likely to be the best order to check for each too):
        /// Abuse Report Original-Mail-From.										[Yahoo]
        /// Message-ID from body part child with content-type of message/rfc822.	[AOL]
        /// Return-Path in main message headers.									[Hotmail]
        /// </summary>
        /// <param name="message">The feedback look email.</param>
        public EmailProcessingDetails ProcessFeedbackLoop(string content)
        {
            EmailProcessingDetails processingDetails = new EmailProcessingDetails();

            MimeMessage message = MimeMessage.Parse(content);

            if (message == null)
            {
                processingDetails.ProcessingResult = EmailProcessingResult.ErrorContent;
                return(processingDetails);
            }

            try
            {
                // Step 1: Yahoo! provide useable Abuse Reports (AOL's are all redacted).
                //Look for abuse report
                BodyPart abuseBodyPart = null;
                if (this.FindFirstBodyPartByMediaType(message.BodyParts, "message/feedback-report", out abuseBodyPart))
                {
                    // Found an abuse report body part to examine.

                    // Abuse report content may have long lines whitespace folded.
                    string abuseReportBody = MimeMessage.UnfoldHeaders(abuseBodyPart.GetDecodedBody());
                    using (StringReader reader = new StringReader(abuseReportBody))
                    {
                        while (reader.Peek() > -1)
                        {
                            string line = reader.ReadToCrLf();

                            // The original mail from value will be the return-path we'd set so we should be able to get all the values we need from that.
                            if (line.StartsWith("Original-Mail-From:", StringComparison.OrdinalIgnoreCase))
                            {
                                string tmp = line.Substring("Original-Mail-From: ".Length - 1);
                                try
                                {
                                    int    internalSendID = -1;
                                    string rcptTo         = string.Empty;

                                    if (ReturnPathManager.TryDecode(tmp, out rcptTo, out internalSendID))
                                    {
                                        // NEED TO LOG TO DB HERE!!!!!
                                        Sends.Send snd = MantaMTA.Core.DAL.SendDB.GetSend(internalSendID);
                                        Save(new MantaAbuseEvent
                                        {
                                            EmailAddress = rcptTo,
                                            EventTime    = DateTime.UtcNow,
                                            EventType    = MantaEventType.Abuse,
                                            SendID       = (snd == null ? string.Empty : snd.ID)
                                        });

                                        processingDetails.ProcessingResult = EmailProcessingResult.SuccessAbuse;
                                        return(processingDetails);
                                    }
                                }
                                catch (Exception)
                                {
                                    // Must be redacted
                                    break;
                                }
                            }
                        }
                    }
                }
                // Function to use against BodyParts to find a return-path header.
                Func <MessageHeaderCollection, bool> checkForReturnPathHeaders = delegate(MessageHeaderCollection headers)
                {
                    MessageHeader returnPathHeader = headers.GetFirstOrDefault("Return-Path");

                    if (returnPathHeader != null && !string.IsNullOrWhiteSpace(returnPathHeader.Value))
                    {
                        int    internalSendID = -1;
                        string rcptTo         = string.Empty;
                        if (ReturnPathManager.TryDecode(returnPathHeader.Value, out rcptTo, out internalSendID))
                        {
                            if (!rcptTo.StartsWith("redacted@", StringComparison.OrdinalIgnoreCase))
                            {
                                // NEED TO LOG TO DB HERE!!!!!
                                Sends.Send snd = MantaMTA.Core.DAL.SendDB.GetSend(internalSendID);
                                Save(new MantaAbuseEvent
                                {
                                    EmailAddress = rcptTo,
                                    EventTime    = DateTime.UtcNow,
                                    EventType    = MantaEventType.Abuse,
                                    SendID       = (snd == null ? string.Empty : snd.ID)
                                });
                                return(true);
                            }
                        }
                    }

                    MessageHeader messageIdHeader = headers.GetFirstOrDefault("Message-ID");
                    if (messageIdHeader != null && messageIdHeader.Value.Length > 33)
                    {
                        string tmp = messageIdHeader.Value.Substring(1, 32);
                        Guid   messageID;
                        if (Guid.TryParse(tmp, out messageID))
                        {
                            int    internalSendID = -1;
                            string rcptTo         = string.Empty;
                            tmp = ReturnPathManager.GetReturnPathFromMessageID(messageID);
                            if (ReturnPathManager.TryDecode(tmp, out rcptTo, out internalSendID))
                            {
                                // NEED TO LOG TO DB HERE!!!!!
                                Sends.Send snd = MantaMTA.Core.DAL.SendDB.GetSend(internalSendID);
                                Save(new MantaAbuseEvent
                                {
                                    EmailAddress = rcptTo,
                                    EventTime    = DateTime.UtcNow,
                                    EventType    = MantaEventType.Abuse,
                                    SendID       = (snd == null ? string.Empty : snd.ID)
                                });
                                return(true);
                            }
                        }
                    }

                    return(false);
                }
                ;


                // Step 2: AOL give redacted Abuse Reports but include the original email as a bodypart; find that.
                BodyPart childMessageBodyPart;
                if (FindFirstBodyPartByMediaType(message.BodyParts, "message/rfc822", out childMessageBodyPart))
                {
                    if (childMessageBodyPart.ChildMimeMessage != null)
                    {
                        if (checkForReturnPathHeaders(childMessageBodyPart.ChildMimeMessage.Headers))
                        {
                            processingDetails.ProcessingResult = EmailProcessingResult.SuccessAbuse;
                            return(processingDetails);
                        }
                    }
                }


                // Step 3: Hotmail don't do Abuse Reports, they just return our email to us exactly as we sent it.
                if (checkForReturnPathHeaders(message.Headers))
                {
                    processingDetails.ProcessingResult = EmailProcessingResult.SuccessAbuse;
                    return(processingDetails);
                }
            }
            catch (Exception) { }

            Logging.Debug("Failed to find return path!");

            processingDetails.ProcessingResult = EmailProcessingResult.ErrorNoReturnPath;
            return(processingDetails);
        }
Exemple #4
0
        /// <summary>
        /// Examines an email to try to identify detailed bounce information from it.
        /// </summary>
        /// <param name="filename">Path and filename of the file being processed.</param>
        /// <param name="message">The entire text content for an email (headers and body).</param>
        /// <returns>An EmailProcessingResult value indicating whether the the email was
        /// successfully processed or not.</returns>
        public EmailProcessingDetails ProcessBounceEmail(string message)
        {
            EmailProcessingDetails bounceDetails = new EmailProcessingDetails();

            MimeMessage msg = MimeMessage.Parse(message);

            if (msg == null)
            {
                bounceDetails.ProcessingResult = EmailProcessingResult.ErrorContent;
                bounceDetails.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce;
                return(bounceDetails);
            }


            // "x-receiver" should contain what Manta originally set as the "return-path" when sending.
            MessageHeader returnPath = msg.Headers.GetFirstOrDefault("x-receiver");

            if (returnPath == null)
            {
                bounceDetails.ProcessingResult = EmailProcessingResult.ErrorNoReturnPath;
                bounceDetails.BounceIdentifier = Core.Enums.BounceIdentifier.UnknownReturnPath;
                return(bounceDetails);
            }

            string rcptTo         = string.Empty;
            int    internalSendID = 0;

            if (!ReturnPathManager.TryDecode(returnPath.Value, out rcptTo, out internalSendID))
            {
                // Not a valid Return-Path so can't process.
                bounceDetails.ProcessingResult = EmailProcessingResult.ErrorNoReturnPath;
                bounceDetails.BounceIdentifier = Core.Enums.BounceIdentifier.UnknownReturnPath;
                return(bounceDetails);
            }

            MantaBounceEvent bounceEvent = new MantaBounceEvent();

            bounceEvent.EmailAddress = rcptTo;
            bounceEvent.SendID       = MantaMTA.Core.DAL.SendDB.GetSend(internalSendID).ID;

            // TODO: Might be good to get the DateTime found in the email.
            bounceEvent.EventTime = DateTime.UtcNow;

            // These properties are both set according to the SMTP code we find, if any.
            bounceEvent.BounceInfo.BounceCode = MantaBounceCode.Unknown;
            bounceEvent.BounceInfo.BounceType = MantaBounceType.Unknown;
            bounceEvent.EventType             = MantaEventType.Bounce;

            // First, try to find a NonDeliveryReport body part as that's the proper way for an MTA
            // to tell us there was an issue sending the email.

            BouncePair bouncePair;
            string     bounceMsg;
            BodyPart   deliveryReportBodyPart;
            string     deliveryReport = string.Empty;

            if (FindFirstBodyPartByMediaType(msg.BodyParts, "message/delivery-status", out deliveryReportBodyPart))
            {
                // If we've got a delivery report, check it for info.

                // Abuse report content may have long lines whitespace folded.
                deliveryReport = MimeMessage.UnfoldHeaders(deliveryReportBodyPart.GetDecodedBody());

                if (ParseNdr(deliveryReport, out bouncePair, out bounceMsg, out bounceDetails))
                {
                    // Successfully parsed.
                    bounceEvent.BounceInfo = bouncePair;
                    bounceEvent.Message    = bounceMsg;

                    // Write BounceEvent to DB.
                    Save(bounceEvent);

                    bounceDetails.ProcessingResult = EmailProcessingResult.SuccessBounce;
                    return(bounceDetails);
                }
            }



            // We're still here so there was either no NDR part or nothing contained within it that we could
            // interpret so have to check _all_ body parts for something useful.
            if (FindBounceReason(msg.BodyParts, out bouncePair, out bounceMsg, out bounceDetails))
            {
                bounceEvent.BounceInfo = bouncePair;
                bounceEvent.Message    = bounceMsg;

                // Write BounceEvent to DB.
                Save(bounceEvent);

                bounceDetails.ProcessingResult = EmailProcessingResult.SuccessBounce;
                return(bounceDetails);
            }



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

            bounceDetails.BounceIdentifier = Core.Enums.BounceIdentifier.NotIdentifiedAsABounce;
            bounceDetails.ProcessingResult = EmailProcessingResult.Unknown;
            return(bounceDetails);
        }
Exemple #5
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);
        }
Exemple #6
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);
        }
Exemple #7
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);
        }
Exemple #8
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);
        }
Exemple #9
0
        /// <summary>
        /// DirectoryHandler provides a standard way of processing a directory of email files.
        /// </summary>
        /// <param name="path">The filepath to operate on.</param>
        /// <param name="fileProcessor">A delegate method that will be used to process each file found in <paramref name="path"/>.</param>
        /// <param name="logger">A delegate method that will be used to return information to an interface, e.g. to
        /// display messages to a user.</param>
        private void DirectoryHandler(string path, Func <string, EmailProcessingDetails> fileProcessor)
        {
            // A filter to use when pulling out files to process; likely to be "*.eml".
            string fileSearchPattern = "*.eml";


            FileInfo[] files = new DirectoryInfo(path).GetFiles(fileSearchPattern);

            // Keep going until there aren't any more files to process, then we wait for the FileSystemWatcher to nudge us
            // back into life again.
            do
            {
                // Loop through and process all the files we've picked up.
                Parallel.ForEach <FileInfo>(files, delegate(FileInfo f)
                {
                    if (_IsStopping)
                    {
                        return;
                    }

                    if (!File.Exists(f.FullName))
                    {
                        Logging.Debug(String.Format("File not found: \"{0}\".", f.FullName));
                        return;
                    }

                    // If a file's not accessible, skip it so we'll pick it up the next time.
                    if (IsFileLocked(f))
                    {
                        return;
                    }

                    string content = File.ReadAllText(f.FullName);



                    // Send the content to the delegate method that'll process its contents.
                    EmailProcessingDetails processingDetails = fileProcessor(content);

                    switch (processingDetails.ProcessingResult)
                    {
                    case EmailProcessingResult.SuccessAbuse:
                        // All good.  Nothing to do other than delete the file.
                        File.Delete(f.FullName);
                        break;

                    case EmailProcessingResult.SuccessBounce:

                        // To enable reviewing of bounce emails and how Manta has processed them, a flag can be set that
                        // keeps processed files.  Files that result in an error when being processed are always kept.
                        if (MtaParameters.KeepBounceFiles)
                        {
                            // Retain files.


                            string keepPath = Path.Combine(Path.GetDirectoryName(f.FullName), _SubdirectoryForProcessedBounceEmails, processingDetails.BounceIdentifier.ToString());

                            switch (processingDetails.BounceIdentifier)
                            {
                            case MantaMTA.Core.Enums.BounceIdentifier.BounceRule:
                                keepPath = Path.Combine(keepPath, processingDetails.MatchingBounceRuleID.ToString());
                                break;

                            case MantaMTA.Core.Enums.BounceIdentifier.NdrCode:
                            case MantaMTA.Core.Enums.BounceIdentifier.SmtpCode:
                                keepPath = Path.Combine(keepPath, processingDetails.MatchingValue);
                                break;
                            }


                            Directory.CreateDirectory(keepPath);

                            File.Move(f.FullName, Path.Combine(keepPath, f.Name));
                        }
                        else
                        {
                            // All good.  Nothing to do other than delete the file.
                            File.Delete(f.FullName);
                        }

                        break;

                    case EmailProcessingResult.ErrorNoFile:
                        throw new FileNotFoundException("Failed to locate file to process: \"" + f.Name + "\".");

                    case EmailProcessingResult.Unknown:
                    case EmailProcessingResult.ErrorContent:
                    case EmailProcessingResult.ErrorNoReason:
                    default:
                        // Move the file into a separate directory, handling any issues of duplicate filenames.
                        bool moved   = false;
                        int version  = 0;
                        Stopwatch sw = new Stopwatch();
                        sw.Start();
                        while (!moved)
                        {
                            try
                            {
                                if (sw.Elapsed > TimeSpan.FromSeconds(10))
                                {
                                    throw new TimeoutException();
                                }

                                if (version > 0)
                                {
                                    File.Move(f.FullName, Path.Combine(Path.GetDirectoryName(f.FullName), _SubdirectoryForProblemEmails, Path.GetFileNameWithoutExtension(f.Name) + "_" + version.ToString() + f.Extension));
                                }
                                else
                                {
                                    File.Move(f.FullName, Path.Combine(Path.GetDirectoryName(f.FullName), _SubdirectoryForProblemEmails, f.Name));
                                }

                                moved = true;
                            }
                            catch (TimeoutException)
                            {
                                Logging.Fatal("Tried to move file " + f.Name + " for 10 seconds but failed.");
                                Logging.Fatal("This is a FATAL failure.");
                                Environment.Exit(-1);
                            }
                            catch (Exception)
                            {
                                Logging.Debug("Attempt " + version.ToString() + " Failed.");
                                version++;
                            }
                        }
                        break;
                    }
                });

                if (!_IsStopping)
                {
                    // Get any new files that have turned up.
                    files = new DirectoryInfo(path).GetFiles(fileSearchPattern);
                }
            }while (files.Count() > 0);
        }