示例#1
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 = BounceIdentifier.NotIdentifiedAsABounce;
                return(bounceDetails);
            }


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

            if (returnPath == null)
            {
                bounceDetails.ProcessingResult = EmailProcessingResult.ErrorNoReturnPath;
                bounceDetails.BounceIdentifier = 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 = BounceIdentifier.UnknownReturnPath;
                return(bounceDetails);
            }

            MantaBounceEvent bounceEvent = new MantaBounceEvent();

            bounceEvent.EmailAddress = rcptTo;
            bounceEvent.SendID       = 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 = BounceIdentifier.NotIdentifiedAsABounce;
            bounceDetails.ProcessingResult = EmailProcessingResult.Unknown;
            return(bounceDetails);
        }
示例#2
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!!!!!
                                        Send snd = 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!!!!!
                                Send snd = 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!!!!!
                                Send snd = 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);
        }
示例#3
0
        /// <summary>
        /// Queues the email for relaying.
        /// </summary>
        private async Task <SmtpServerTransactionAsyncResult> QueueForRelayingAsync()
        {
            // The email is for relaying.
            Guid messageID = Guid.NewGuid();

            // Look for any MTA control headers.
            MessageHeaderCollection headers = MessageManager.GetMessageHeaders(Data);

            // Will not be null if the SendGroupID header was present.
            MessageHeader ipGroupHeader = headers.SingleOrDefault(m => m.Name.Equals(MessageHeaderNames.SendGroupID, StringComparison.OrdinalIgnoreCase));

            // Parameter will hold the MtaIPGroup that will be used to relay this message.
            VirtualMtaGroup mtaGroup  = null;
            int             ipGroupID = 0;

            if (ipGroupHeader != null)
            {
                if (int.TryParse(ipGroupHeader.Value, out ipGroupID))
                {
                    mtaGroup = VirtualMtaManager.GetVirtualMtaGroup(ipGroupID);
                }
            }

            #region Look for a send id, if one doesn't exist create it.
            MessageHeader sendIdHeader   = headers.SingleOrDefault(h => h.Name.Equals(MessageHeaderNames.SendID, StringComparison.OrdinalIgnoreCase));
            int           internalSendId = -1;
            if (sendIdHeader != null)
            {
                Send sndID = await SendManager.Instance.GetSendAsync(sendIdHeader.Value);

                if (sndID.SendStatus == SendStatus.Discard)
                {
                    return(SmtpServerTransactionAsyncResult.FailedSendDiscarding);
                }
                internalSendId = sndID.InternalID;
            }
            else
            {
                Send sndID = await SendManager.Instance.GetDefaultInternalSendIdAsync();

                if (sndID.SendStatus == SendStatus.Discard)
                {
                    return(SmtpServerTransactionAsyncResult.FailedSendDiscarding);
                }
                internalSendId = sndID.InternalID;
            }
            #endregion

            #region Generate Return Path
            string returnPath = string.Empty;

            // Can only return path to messages with one rcpt to
            if (RcptTo.Count == 1)
            {
                // Need to check to see if the message contains a return path overide domain.
                MessageHeader returnPathDomainOverrideHeader = headers.SingleOrDefault(h => h.Name.Equals(MessageHeaderNames.ReturnPathDomain, StringComparison.OrdinalIgnoreCase));

                if (returnPathDomainOverrideHeader != null &&
                    MtaParameters.LocalDomains.Count(d => d.Hostname.Equals(returnPathDomainOverrideHeader.Value, StringComparison.OrdinalIgnoreCase)) > 0)
                {
                    // The message contained a local domain in the returnpathdomain
                    // header so use it instead of the default.
                    returnPath = ReturnPathManager.GenerateReturnPath(RcptTo[0], internalSendId, returnPathDomainOverrideHeader.Value);
                }
                else
                {
                    // The message didn't specify a return path overide or it didn't
                    // contain a localdomain so use the default.
                    returnPath = ReturnPathManager.GenerateReturnPath(RcptTo[0], internalSendId);
                }

                // Insert the return path header.
                Data = MessageManager.AddHeader(Data, new MessageHeader("Return-Path", "<" + returnPath + ">"));
            }
            else
            {
                // multiple rcpt's so can't have unique return paths, use generic mail from.
                returnPath = MailFrom;
            }
            #endregion

            #region Generate a message ID header
            string msgIDHeaderVal = "<" + messageID.ToString("N") + MailFrom.Substring(MailFrom.LastIndexOf("@")) + ">";

            // If there is already a message header, remove it and add our own. required for feedback loop processing.
            if (headers.Count(h => h.Name.Equals("Message-ID", StringComparison.OrdinalIgnoreCase)) > 0)
            {
                Data = MessageManager.RemoveHeader(Data, "Message-ID");
            }

            // Add the new message-id header.
            Data = MessageManager.AddHeader(Data, new MessageHeader("Message-ID", msgIDHeaderVal));
            #endregion

            #region Get message priority
            var msgPriority    = RabbitMqPriority.Low;
            var priorityHeader = headers.GetFirstOrDefault(MessageHeaderNames.Priority);
            if (priorityHeader != null)
            {
                var outVal = 0;
                if (int.TryParse(priorityHeader.Value, out outVal))
                {
                    if (outVal >= 0)
                    {
                        msgPriority = outVal < 3 ? (RabbitMqPriority)(byte)outVal
                                                 : RabbitMqPriority.High;
                    }
                }
            }
            #endregion

            // Remove any control headers.
            headers = new MessageHeaderCollection(headers.Where(h => h.Name.StartsWith(MessageHeaderNames.HeaderNamePrefix, StringComparison.OrdinalIgnoreCase)));
            foreach (MessageHeader header in headers)
            {
                Data = MessageManager.RemoveHeader(Data, header.Name);
            }

            // If the MTA group doesn't exist or it's not got any IPs, use the default.
            if (mtaGroup == null ||
                mtaGroup.VirtualMtaCollection.Count == 0)
            {
                ipGroupID = VirtualMtaManager.GetDefaultVirtualMtaGroup().ID;
            }

            // Attempt to Enqueue the Email for Relaying.
            var enqueued = await QueueManager.Instance.Enqueue(messageID, ipGroupID, internalSendId, returnPath, RcptTo.ToArray(), Data, msgPriority);

            return(enqueued ? SmtpServerTransactionAsyncResult.SuccessMessageQueued
                            : SmtpServerTransactionAsyncResult.FailedToEnqueue);
        }