/// <summary> /// Gets a single Virtual MTA Group. /// </summary> /// <param name="id">ID of the Virtual MTA Group to get.</param> /// <returns>The Virtual MTA Group or NULL if none exist with ID</returns> public static VirtualMtaGroup GetVirtualMtaGroup(int id) { VirtualMtaGroup grp = VirtualMtaGroupDB.GetVirtualMtaGroup(id); grp.VirtualMtaCollection = VirtualMtaDB.GetVirtualMtasInVirtualMtaGroup(grp.ID); return(grp); }
/// <summary> /// Creates a MtaIPGroup object using the Data Record. /// </summary> /// <param name="record"></param> /// <returns></returns> private static VirtualMtaGroup CreateAndFillVirtualMtaGroup(IDataRecord record) { VirtualMtaGroup group = new VirtualMtaGroup(); group.ID = record.GetInt32("ip_group_id"); group.Name = record.GetString("ip_group_name"); if (!record.IsDBNull("ip_group_description")) { group.Description = record.GetString("ip_group_description"); } return(group); }
/// <summary> /// Saves the virtual mta group to the database. /// </summary> /// <param name="grp">Group to save.</param> public static void Save(VirtualMtaGroup grp) { StringBuilder groupMembershipInserts = new StringBuilder(); foreach (VirtualMTA vmta in grp.VirtualMtaCollection) { groupMembershipInserts.AppendFormat(@"{1}INSERT INTO man_ip_groupMembership(ip_group_id, ip_ipAddress_id) VALUES(@id,{0}){1}", vmta.ID, Environment.NewLine); } using (SqlConnection conn = MantaDB.GetSqlConnection()) { SqlCommand cmd = conn.CreateCommand(); cmd.CommandText = @" BEGIN TRANSACTION IF EXISTS(SELECT 1 FROM man_ip_group WHERE ip_group_id = @id) UPDATE man_ip_group SET ip_group_name = @name, ip_group_description = @description WHERE ip_group_id = @id ELSE BEGIN INSERT INTO man_ip_group(ip_group_name, ip_group_description) VALUES(@name, @description) SELECT @id = @@IDENTITY END DELETE FROM man_ip_groupMembership WHERE ip_group_id = @id " + groupMembershipInserts.ToString() + @" COMMIT TRANSACTION"; cmd.Parameters.AddWithValue("@id", grp.ID); cmd.Parameters.AddWithValue("@name", grp.Name); if (grp.Description == null) { cmd.Parameters.AddWithValue("@description", DBNull.Value); } else { cmd.Parameters.AddWithValue("@description", grp.Description); } conn.Open(); cmd.ExecuteNonQuery(); } }
/// <summary> /// Gets the specfied MTA IP Group /// </summary> /// <param name="id">ID of the group to get.</param> /// <returns>The IP Group or NULL if doesn't exist.</returns> public static VirtualMtaGroup GetVirtualMtaGroup(int id) { VirtualMtaGroup group = null; // Try and get IPGroup from the in memory collection. if (_vmtaGroups.TryGetValue(id, out group)) { // Only cache IP Groups for N minutes. if (group.CreatedTimestamp.AddMinutes(MtaParameters.MTA_CACHE_MINUTES) > DateTime.UtcNow) { return(group); } } // We need to goto the database to get the group. Lock! lock (_MtaVirtualMtaGroupSyncLock) { // Check that something else didn't already load from the database. // If it did then we can just return that. _vmtaGroups.TryGetValue(id, out group); if (group != null && group.CreatedTimestamp.AddMinutes(MtaParameters.MTA_CACHE_MINUTES) > DateTime.UtcNow) { return(group); } // Get group from the database. group = VirtualMtaGroupDB.GetVirtualMtaGroup(id); // Group doesn't exist, so don't try and get it's IPs if (group == null) { return(null); } // Got the group, go get it's IPs. group.VirtualMtaCollection = VirtualMtaDB.GetVirtualMtasInVirtualMtaGroup(id); // Add the group to collection, so others can use it. _vmtaGroups.AddOrUpdate(id, group, new Func <int, VirtualMtaGroup, VirtualMtaGroup>(delegate(int key, VirtualMtaGroup existing) { return(group); })); return(group); } }
// // GET: /VirtualMta/EditGroup public ActionResult EditGroup(int id = WebInterfaceParameters.VIRTUALMTAGROUP_NEW_ID) { VirtualMtaGroup grp = null; if (id == WebInterfaceParameters.VIRTUALMTAGROUP_NEW_ID) { grp = new VirtualMtaGroup(); } else { grp = VirtualMtaWebManager.GetVirtualMtaGroup(id); } return(View(new VirtualMtaGroupCreateEditModel { VirtualMtaGroup = grp, VirtualMTACollection = VirtualMtaDB.GetVirtualMtas() })); }
public bool SaveGroup(SaveVirtualMtaGroupViewModel viewModel) { VirtualMtaGroup grp = null; if (viewModel.Id == WebInterfaceParameters.VIRTUALMTAGROUP_NEW_ID) { grp = new VirtualMtaGroup(); } else { grp = VirtualMtaGroupDB.GetVirtualMtaGroup(viewModel.Id); } if (grp == null) { return(false); } grp.Name = viewModel.Name; grp.Description = viewModel.Description; var vMtas = VirtualMtaDB.GetVirtualMtas(); for (int i = 0; i < viewModel.MtaIDs.Length; i++) { VirtualMTA mta = vMtas.SingleOrDefault(m => m.ID == viewModel.MtaIDs[i]); if (mta == null) { return(false); } grp.VirtualMtaCollection.Add(mta); } VirtualMtaWebManager.Save(grp); return(true); }
public bool SaveGroup(int id, string name, string description, int[] mtaIDs) { VirtualMtaGroup grp = null; if (id == WebInterfaceParameters.VIRTUALMTAGROUP_NEW_ID) { grp = new VirtualMtaGroup(); } else { grp = MantaMTA.Core.DAL.VirtualMtaGroupDB.GetVirtualMtaGroup(id); } if (grp == null) { return(false); } grp.Name = name; grp.Description = description; VirtualMTACollection vMtas = MantaMTA.Core.DAL.VirtualMtaDB.GetVirtualMtas(); for (int i = 0; i < mtaIDs.Length; i++) { VirtualMTA mta = vMtas.SingleOrDefault(m => m.ID == mtaIDs[i]); if (mta == null) { return(false); } grp.VirtualMtaCollection.Add(mta); } VirtualMtaWebManager.Save(grp); return(true); }
/// <summary> /// /// </summary> /// <param name="vMtaGroup"></param> /// <param name="mxRecord"></param> /// <param name="rawMsg"></param> /// <returns></returns> public async Task <MantaOutboundClientSendResult> SendAsync(MailAddress mailFrom, MailAddress rcptTo, VirtualMtaGroup vMtaGroup, MXRecord[] mxRecord, string msg) { Logging.Debug("MantaOutboundClientPoolCollection.SendAsync> From: " + mailFrom + " To:" + rcptTo + " via:" + vMtaGroup.Name); var result = null as MantaOutboundClientSendResult; foreach (var vmta in vMtaGroup.VirtualMtaCollection) { result = await SendAsync(mailFrom, rcptTo, vmta, mxRecord.OrderBy(mx => mx.Preference).First(), msg); switch (result.MantaOutboundClientResult) { case MantaOutboundClientResult.FailedToConnect: case MantaOutboundClientResult.RejectedByRemoteServer: case MantaOutboundClientResult.Success: return(result); case MantaOutboundClientResult.ClientAlreadyInUse: case MantaOutboundClientResult.MaxConnections: case MantaOutboundClientResult.MaxMessages: case MantaOutboundClientResult.ServiceNotAvalible: continue; } } return(result); }
/// <summary> /// Saves the Virtual MTA Group. /// </summary> /// <param name="grp">Virtual MTA Group to save.</param> public static void Save(VirtualMtaGroup grp) { VirtualMtaGroupDB.Save(grp); }
private async Task <bool> SendMessageAsync(MtaQueuedMessage msg) { // Check that the message next attempt after has passed. if (msg.AttemptSendAfterUtc > DateTime.UtcNow) { RabbitMqOutboundQueueManager.Enqueue(msg); await Task.Delay(50); // To prevent a tight loop within a Task thread we should sleep here. return(false); } if (await 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 msg.HandleMessageDiscardAsync(); 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: msg.HandleSendPaused(); 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); 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 msg.HandleDeliveryFailAsync(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 msg.HandleDeliveryFailAsync("550 Domain Not Found.", null, null); result = false; } else if (IsMxBlacklisted(mXRecords)) { await msg.HandleDeliveryFailAsync("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 msg.HandleFailedToConnectAsync(sndIpAddress, mXRecords[0]); break; case SmtpOutboundClientDequeueAsyncResult.ServiceUnavalible: await msg.HandleServiceUnavailableAsync(sndIpAddress); break; case SmtpOutboundClientDequeueAsyncResult.Throttled: await msg.HandleDeliveryThrottleAsync(sndIpAddress, mXRecords[0]); break; case SmtpOutboundClientDequeueAsyncResult.FailedMaxConnections: msg.AttemptSendAfterUtc = DateTime.UtcNow.AddSeconds(2); 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")) { msg.HandleDeliveryFailAsync(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); msg.HandleDeliveryDeferral(smtpResponse, sndIpAddress, smtpClient.MXRecord, true); } else { // Otherwise message is deferred msg.HandleDeliveryDeferral(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 msg.HandleDeliverySuccessAsync(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) { msg.HandleDeliveryDeferral("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); }
/// <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); }