/// <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); }
private async Task SendMessageAsync(MtaQueuedMessage msg) { // Check that the message next attempt after has passed. if (msg.AttemptSendAfterUtc > DateTime.UtcNow) { await RabbitMqOutboundQueueManager.Enqueue(msg); await Task.Delay(50); // To prevent a tight loop within a Task thread we should sleep here. return; } if (await Data.MtaTransaction.HasBeenHandledAsync(msg.ID)) { msg.IsHandled = true; return; } // Get the send that this message belongs to so that we can check the send state. var snd = await SendManager.Instance.GetSendAsync(msg.InternalSendID); switch (snd.SendStatus) { // The send is being discarded so we should discard the message. case SendStatus.Discard: await MtaMessageHelper.HandleMessageDiscardAsync(msg); return; // The send is paused, the handle pause state will delay, without deferring, the message for a while so we can move on to other messages. case SendStatus.Paused: await MtaMessageHelper.HandleSendPaused(msg); return; // Send is active so we don't need to do anything. case SendStatus.Active: break; // Unknown send state, requeue the message and log error. Cannot send! default: msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); await RabbitMqOutboundQueueManager.Enqueue(msg); Logging.Error("Failed to send message. Unknown SendStatus[" + snd.SendStatus + "]!"); return; } // Check the message hasn't timed out. If it has don't attempt to send it. // Need to do this here as there may be a massive backlog on the server // causing messages to be waiting for ages after there AttemptSendAfter // before picking up. The MAX_TIME_IN_QUEUE should always be enforced. if (msg.AttemptSendAfterUtc - msg.QueuedTimestampUtc > new TimeSpan(0, MtaParameters.MtaMaxTimeInQueue, 0)) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, MtaParameters.TIMED_OUT_IN_QUEUE_MESSAGE, null, null); } else { MailAddress rcptTo = new MailAddress(msg.RcptTo[0]); MailAddress mailFrom = new MailAddress(msg.MailFrom); MXRecord[] mXRecords = DNSManager.GetMXRecords(rcptTo.Host); // If mxs is null then there are no MX records. if (mXRecords == null || mXRecords.Length < 1) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, "550 Domain Not Found.", null, null); } else if (IsMxBlacklisted(mXRecords)) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, "550 Domain blacklisted.", null, mXRecords[0]); } else { var vMtaGroup = VirtualMtaManager.GetVirtualMtaGroup(msg.VirtualMTAGroupID); var sendResult = await MantaSmtpClientPoolCollection.Instance.SendAsync(mailFrom, rcptTo, vMtaGroup, mXRecords, msg.Message); switch (sendResult.MantaOutboundClientResult) { case MantaOutboundClientResult.FailedToConnect: await MtaMessageHelper.HandleFailedToConnectAsync(msg, sendResult.VirtualMTA, sendResult.MXRecord); break; case MantaOutboundClientResult.MaxConnections: await RabbitMqOutboundQueueManager.Enqueue(msg); break; case MantaOutboundClientResult.MaxMessages: await MtaMessageHelper.HandleDeliveryThrottleAsync(msg, sendResult.VirtualMTA, sendResult.MXRecord); break; case MantaOutboundClientResult.RejectedByRemoteServer: if (string.IsNullOrWhiteSpace(sendResult.Message)) { Logging.Error("RejectedByRemoteServer but no message!"); await MtaMessageHelper.HandleDeliveryDeferralAsync(msg, sendResult.Message, sendResult.VirtualMTA, sendResult.MXRecord); } if (sendResult.Message[0] == '4') { await MtaMessageHelper.HandleDeliveryDeferralAsync(msg, sendResult.Message, sendResult.VirtualMTA, sendResult.MXRecord); } else { await MtaMessageHelper.HandleDeliveryFailAsync(msg, sendResult.Message, sendResult.VirtualMTA, sendResult.MXRecord); } break; case MantaOutboundClientResult.ServiceNotAvalible: await MtaMessageHelper.HandleServiceUnavailableAsync(msg, sendResult.VirtualMTA); break; case MantaOutboundClientResult.Success: await MtaMessageHelper.HandleDeliverySuccessAsync(msg, sendResult.VirtualMTA, sendResult.MXRecord, sendResult.Message); break; default: // Something weird happening with this message, get it out of the way for a bit. msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(5); await RabbitMqOutboundQueueManager.Enqueue(msg); break; } } } }
private async Task <bool> SendMessageAsync(MtaQueuedMessage msg) { // Check that the message next attempt after has passed. if (msg.AttemptSendAfterUtc > DateTime.UtcNow) { await RabbitMqOutboundQueueManager.Enqueue(msg); await Task.Delay(50); // To prevent a tight loop within a Task thread we should sleep here. return(false); } if (await Data.MtaTransaction.HasBeenHandledAsync(msg.ID)) { msg.IsHandled = true; return(true); } // Get the send that this message belongs to so that we can check the send state. Send snd = await SendManager.Instance.GetSendAsync(msg.InternalSendID); switch (snd.SendStatus) { // The send is being discarded so we should discard the message. case SendStatus.Discard: await MtaMessageHelper.HandleMessageDiscardAsync(msg); return(false); // The send is paused, the handle pause state will delay, without deferring, the message for a while so we can move on to other messages. case SendStatus.Paused: await MtaMessageHelper.HandleSendPaused(msg); return(false); // Send is active so we don't need to do anything. case SendStatus.Active: break; // Unknown send state, requeue the message and log error. Cannot send! default: msg.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); await RabbitMqOutboundQueueManager.Enqueue(msg); Logging.Error("Failed to send message. Unknown SendStatus[" + snd.SendStatus + "]!"); return(false); } bool result; // Check the message hasn't timed out. If it has don't attempt to send it. // Need to do this here as there may be a massive backlog on the server // causing messages to be waiting for ages after there AttemptSendAfter // before picking up. The MAX_TIME_IN_QUEUE should always be enforced. if (msg.AttemptSendAfterUtc - msg.QueuedTimestampUtc > new TimeSpan(0, MtaParameters.MtaMaxTimeInQueue, 0)) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, MtaParameters.TIMED_OUT_IN_QUEUE_MESSAGE, null, null); result = false; } else { MailAddress mailAddress = new MailAddress(msg.RcptTo[0]); MailAddress mailFrom = new MailAddress(msg.MailFrom); MXRecord[] mXRecords = DNSManager.GetMXRecords(mailAddress.Host); // If mxs is null then there are no MX records. if (mXRecords == null || mXRecords.Length < 1) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, "550 Domain Not Found.", null, null); result = false; } else if (IsMxBlacklisted(mXRecords)) { await MtaMessageHelper.HandleDeliveryFailAsync(msg, "550 Domain blacklisted.", null, mXRecords[0]); result = false; } else { // The IP group that will be used to send the queued message. VirtualMtaGroup virtualMtaGroup = VirtualMtaManager.GetVirtualMtaGroup(msg.VirtualMTAGroupID); VirtualMTA sndIpAddress = virtualMtaGroup.GetVirtualMtasForSending(mXRecords[0]); SmtpOutboundClientDequeueResponse dequeueResponse = await SmtpClientPool.Instance.DequeueAsync(sndIpAddress, mXRecords); switch (dequeueResponse.DequeueResult) { case SmtpOutboundClientDequeueAsyncResult.Success: case SmtpOutboundClientDequeueAsyncResult.NoMxRecords: case SmtpOutboundClientDequeueAsyncResult.FailedToAddToSmtpClientQueue: case SmtpOutboundClientDequeueAsyncResult.Unknown: break; // Don't need to do anything for these results. case SmtpOutboundClientDequeueAsyncResult.FailedToConnect: await MtaMessageHelper.HandleFailedToConnectAsync(msg, sndIpAddress, mXRecords[0]); break; case SmtpOutboundClientDequeueAsyncResult.ServiceUnavalible: await MtaMessageHelper.HandleServiceUnavailableAsync(msg, sndIpAddress); break; case SmtpOutboundClientDequeueAsyncResult.Throttled: await MtaMessageHelper.HandleDeliveryThrottleAsync(msg, sndIpAddress, mXRecords[0]); break; case SmtpOutboundClientDequeueAsyncResult.FailedMaxConnections: msg.AttemptSendAfterUtc = DateTime.UtcNow.AddSeconds(2); await RabbitMqOutboundQueueManager.Enqueue(msg); break; } SmtpOutboundClient smtpClient = dequeueResponse.SmtpOutboundClient; // If no client was dequeued then we can't currently send. // This is most likely a max connection issue. Return false but don't // log any deferal or throttle. if (smtpClient == null) { result = false; } else { try { Action <string> failedCallback = delegate(string smtpResponse) { // If smtpRespose starts with 5 then perm error should cause fail if (smtpResponse.StartsWith("5")) { MtaMessageHelper.HandleDeliveryFailAsync(msg, smtpResponse, sndIpAddress, smtpClient.MXRecord).Wait(); } else { // If the MX is actively denying use service access, SMTP code 421 then we should inform // the ServiceNotAvailableManager manager so it limits our attepts to this MX to 1/minute. if (smtpResponse.StartsWith("421")) { ServiceNotAvailableManager.Add(smtpClient.SmtpStream.LocalAddress.ToString(), smtpClient.MXRecord.Host, DateTime.UtcNow); MtaMessageHelper.HandleDeliveryDeferral(msg, smtpResponse, sndIpAddress, smtpClient.MXRecord, true); } else { // Otherwise message is deferred MtaMessageHelper.HandleDeliveryDeferral(msg, smtpResponse, sndIpAddress, smtpClient.MXRecord, false); } } throw new SmtpTransactionFailedException(); }; // Run each SMTP command after the last. await smtpClient.ExecHeloOrRsetAsync(failedCallback); await smtpClient.ExecMailFromAsync(mailFrom, failedCallback); await smtpClient.ExecRcptToAsync(mailAddress, failedCallback); await smtpClient.ExecDataAsync(msg.Message, failedCallback, async (response) => { await MtaMessageHelper.HandleDeliverySuccessAsync(msg, sndIpAddress, smtpClient.MXRecord, response); }); SmtpClientPool.Instance.Enqueue(smtpClient); result = true; } catch (SmtpTransactionFailedException) { // Exception is thrown to exit transaction, logging of deferrals/failers already handled. result = false; } catch (Exception ex) { Logging.Error("MessageSender error.", ex); if (msg != null) { MtaMessageHelper.HandleDeliveryDeferral(msg, "Connection was established but ended abruptly.", sndIpAddress, smtpClient.MXRecord, false); } result = false; } finally { if (smtpClient != null) { smtpClient.IsActive = false; smtpClient.LastActive = DateTime.UtcNow; } } } } } return(result); }