public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; string[] relayingIPs = MtaParameters.IPsToAllowRelaying; if (!relayingIPs.Contains(context.Request.UserHostAddress)) { context.Response.Write("Forbidden"); return; } string sendID = context.Request.QueryString["SendID"]; string name = context.Request.QueryString["Name"]; string value = context.Request.QueryString["Value"]; if (string.IsNullOrWhiteSpace(sendID) || string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(value)) { context.Response.Write("bad"); return; } Send snd = SendManager.Instance.GetSend(sendID); SendDB.SaveSendMetadata(snd.InternalID, new SendMetadata { Name = name, Value = value }); context.Response.Write("ok"); }
// // GET: /Sends/ public ActionResult Index(int page = 1, int pageSize = 10) { SendInfoCollection sends = SendDB.GetSends(pageSize, page); int pages = (int)Math.Ceiling(SendDB.GetSendsCount() / Convert.ToDouble(pageSize)); return(View(new SendsModel(sends, page, pages))); }
// // GET: /Dashboard/ public ActionResult Index() { DashboardModel model = new DashboardModel { SendTransactionSummaryCollection = TransactionDB.GetLastHourTransactionSummary(), Waiting = SendDB.GetQueueCount(new SendStatus[] { SendStatus.Active, SendStatus.Discard }), Paused = SendDB.GetQueueCount(new SendStatus[] { SendStatus.Paused }), BounceInfo = TransactionDB.GetLastHourBounceInfo(3), SendSpeedInfo = TransactionDB.GetLastHourSendSpeedInfo() }; try { // Connect to Rabbit MQ and grab basic queue counts. HttpWebRequest request = HttpWebRequest.CreateHttp("http://localhost:15672/api/queues"); request.Credentials = new NetworkCredential(MtaParameters.RabbitMQ.Username, MtaParameters.RabbitMQ.Password); using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { string json = new StreamReader(response.GetResponseStream()).ReadToEnd(); JArray rabbitQueues = JArray.Parse(json); foreach (JToken q in rabbitQueues.Children()) { JEnumerable <JProperty> qProperties = q.Children <JProperty>(); string queueName = (string)qProperties.First(x => x.Name.Equals("name")).Value; if (queueName.StartsWith("manta_mta_")) { long messages = (long)qProperties.First(x => x.Name.Equals("messages", System.StringComparison.OrdinalIgnoreCase)).Value; if (queueName.IndexOf("_inbound") > 0) { model.RabbitMqInbound += messages; } else if (queueName.IndexOf("_outbound_") > 0) { model.RabbitMqTotalOutbound += messages; } } } } } catch (Exception) { model.RabbitMqInbound = int.MinValue; model.RabbitMqTotalOutbound = int.MinValue; } return(View(model)); }
/// <summary> /// Gets the specified Send. /// </summary> /// <param name="internalSendID">Internal ID of the Send.</param> /// <returns>The Send.</returns> public async Task <Send> GetSendAsync(int internalSendID) { // Don't want send IDs sitting in memory for to long so clear every so often. if (this._SendsLastCleared.AddSeconds(10) < DateTime.UtcNow) { this.ClearSendsCache(); } Send snd; // Try to get the send id from the cached collection. if (!this._SendsInternalID.TryGetValue(internalSendID, out snd)) { // Doesn't exist so need to create or load from datbase. snd = await SendDB.GetSendAsync(internalSendID); // Add are new item to the cache. this._SendsInternalID.TryAdd(internalSendID, snd); this._Sends.TryAdd(snd.ID, snd); } return(snd); }
/// <summary> /// Gets Send with the specified sendID. /// If it doesn't exist it will be created in the database. /// </summary> /// <param name="sendId">ID of the Send.</param> /// <returns>The Send.</returns> public async Task <Send> GetSendAsync(string sendId) { // Don't want send IDs sitting in memory for to long so clear every so often. if (this._SendsLastCleared.AddSeconds(60) < DateTime.UtcNow) { this.ClearSendsCache(); } Send snd; // Try to get the send id from the cached collection. if (!this._Sends.TryGetValue(sendId, out snd)) { // Doesn't exist so need to create or load from datbase. snd = await SendDB.CreateAndGetInternalSendIDAsync(sendId).ConfigureAwait(false); // Add are new item to the cache. this._Sends.TryAdd(sendId, snd); this._SendsInternalID.TryAdd(snd.InternalID, snd); } // return the value. return(snd); }
public void TimedOutInQueue() { using (System.Transactions.TransactionScope ts = CreateTransactionScopeObject()) { MantaEventCollection events = EventsManager.Instance.GetEvents(); int initialMaxEventID = events.Max(e => e.ID); bool result = false; EmailProcessingDetails processingDetails; result = EventsManager.Instance.ProcessSmtpResponseMessage(MtaParameters.TIMED_OUT_IN_QUEUE_MESSAGE, "*****@*****.**", 1, out processingDetails); Assert.IsTrue(result); Assert.IsNull(processingDetails); // Check an Event has been created. events = EventsManager.Instance.GetEvents(); int newMaxEventID = events.Max(e => e.ID); Assert.AreNotEqual(initialMaxEventID, newMaxEventID); MantaEvent ev = events.First(e => e.ID == newMaxEventID); // Check the new Event record. Assert.IsTrue(ev is MantaTimedOutInQueueEvent); Assert.AreEqual(ev.ID, newMaxEventID); Assert.AreEqual(ev.EventType, MantaEventType.TimedOutInQueue); Assert.AreEqual(ev.EmailAddress, "*****@*****.**"); Assert.AreEqual(ev.SendID, SendDB.GetSend(1).ID); Assert.That(ev.EventTime, Is.EqualTo(DateTime.UtcNow).Within(TimeSpan.FromSeconds(1))); // Depends on how long it takes to get the Events out of the DB. Assert.IsFalse(ev.Forwarded); } }
/// <summary> /// Sets the status of the specified send to the specified status. /// </summary> /// <param name="sendID">ID of the send to set the staus of.</param> /// <param name="status">The status to set the send to.</param> public void SetSendStatus(string sendID, SendStatus status) { SendDB.SetSendStatus(sendID, status); }
/// <summary> /// Sets the status of the specified send to the specified status. /// </summary> /// <param name="sendID">ID of the send to set the staus of.</param> /// <param name="status">The status to set the send to.</param> internal void SetSendStatus(string sendID, SendStatus status) { SendDB.SetSendStatus(sendID, status); }
/// <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); }
/// <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); }
/// <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 = BounceIdentifier.NotIdentifiedAsABounce; return(false); }
// // GET: /Sends/Overview?sendID= public ActionResult Overview(string sendID) { SendInfo send = SendDB.GetSend(sendID); return(View(new SendReportOverview(send))); }
// // GET: /Queue/ public ActionResult Queue() { SendInfoCollection sends = SendDB.GetSendsInProgress(); return(View(new SendsModel(sends, 1, 1))); }