/// <summary> /// This method handle successful delivery. /// Logs success /// Deletes queued data /// </summary> public async Task <bool> HandleDeliverySuccessAsync(VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord, string response) { await MtaTransaction.LogTransactionAsync(this, TransactionStatus.Success, response, ipAddress, mxRecord); IsHandled = true; return(true); }
/// <summary> /// Handles a service unavailable event, should be same as defer but only wait 1 minute before next retry. /// </summary> /// <param name="sndIpAddress"></param> internal async Task <bool> HandleServiceUnavailableAsync(VirtualMta.VirtualMTA ipAddress) { // Log deferral await MtaTransaction.LogTransactionAsync(this, TransactionStatus.Deferred, "Service Unavailable", ipAddress, null); // Set next retry time and release the lock. this.AttemptSendAfterUtc = DateTime.UtcNow.AddSeconds(15); Requeue(); return(true); }
/// <summary> /// This method handles message throttle. /// Logs throttle /// Sets the next rety date time /// </summary> internal async Task <bool> HandleDeliveryThrottleAsync(VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord) { // Log deferral await MtaTransaction.LogTransactionAsync(this, TransactionStatus.Throttled, string.Empty, ipAddress, mxRecord); // Set next retry time and release the lock. this.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(1); Requeue(); return(true); }
/// <summary> /// Creates a VirtualMTA object filled with the values from the DataRecord. /// </summary> /// <param name="record"></param> /// <returns></returns> private static VirtualMta.VirtualMTA CreateAndFillVirtualMtaFromRecord(IDataRecord record) { VirtualMta.VirtualMTA vmta = new VirtualMta.VirtualMTA(); vmta.ID = record.GetInt32("ip_ipAddress_id"); vmta.Hostname = record.GetString("ip_ipAddress_hostname"); vmta.IPAddress = System.Net.IPAddress.Parse(record.GetString("ip_ipAddress_ipAddress")); vmta.IsSmtpInbound = record.GetBoolean("ip_ipAddress_isInbound"); vmta.IsSmtpOutbound = record.GetBoolean("ip_ipAddress_isOutbound"); return(vmta); }
/// <summary> /// Creates a SmtpOutboundClient bound to the specified endpoint. /// </summary> /// <param name="ipAddress">The local IP address to bind to.</param> public SmtpOutboundClient(VirtualMta.VirtualMTA ipAddress) : base(new IPEndPoint(ipAddress.IPAddress, 0)) { this.IsActive = true; this.MtaIpAddress = ipAddress; base.ReceiveTimeout = MtaParameters.Client.ConnectionReceiveTimeoutInterval * 1000; base.SendTimeout = MtaParameters.Client.ConnectionSendTimeoutInterval * 1000; base.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); SmtpOutboundClientCollection.Instance.Add(this); this.IsActive = false; this._CanPipeline = false; }
/// <summary> /// /// </summary> /// <param name="ipAddress"></param> /// <param name="mxRecord"></param> /// <returns></returns> public async Task <bool> HandleFailedToConnectAsync(VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord) { // If there was no MX record in DNS, so using A, we should fail and not retry. if (mxRecord.MxRecordSrc == DNS.MxRecordSrc.A) { return(await HandleDeliveryFailAsync("550 Failed to connect", ipAddress, mxRecord)); } else { return(await HandleDeliveryDeferralAsync("Failed to connect", ipAddress, mxRecord, false, 15)); } }
/// <summary> /// This method handles message deferal. /// Logs deferral /// Fails the message if timed out /// or /// Sets the next rety date time /// </summary> /// <param name="defMsg">The deferal message from the SMTP server.</param> /// <param name="ipAddress">IP Address that send was attempted from.</param> /// <param name="mxRecord">MX Record of the server tried to send too.</param> /// <param name="isServiceUnavailable">If false will backoff the retry, if true will use the MtaParameters.MtaRetryInterval, /// this is needed to reduce the tail when sending as a message could get multiple try again laters and soon be 1h+ before next retry.</param> public async Task <bool> HandleDeliveryDeferralAsync(string defMsg, VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord, bool isServiceUnavailable = false, int?overrideTimeminutes = null) { // Log the deferral. await MtaTransaction.LogTransactionAsync(this, TransactionStatus.Deferred, defMsg, ipAddress, mxRecord); // This holds the maximum interval between send retries. Should be put in the database. int maxInterval = 3 * 60; // Increase the defered count as the queued messages has been deferred. DeferredCount++; // Hold the minutes to wait until next retry. double nextRetryInterval = MtaParameters.MtaRetryInterval; if (overrideTimeminutes.HasValue) { nextRetryInterval = overrideTimeminutes.Value; } else { if (!isServiceUnavailable) { // Increase the deferred wait interval by doubling for each retry. for (int i = 1; i < DeferredCount; i++) { nextRetryInterval = nextRetryInterval * 2; } // If we have gone over the max interval then set to the max interval value. if (nextRetryInterval > maxInterval) { nextRetryInterval = maxInterval; } } else { nextRetryInterval = 1; // For service unavalible use 1 minute between retries. } } // Set next retry time and release the lock. this.AttemptSendAfterUtc = DateTime.UtcNow.AddMinutes(nextRetryInterval); Requeue(); return(true); }
public void SmtpClientMaxMessages() { using (SmtpServer s = new SmtpServer(25)) { VirtualMta.VirtualMTA ipAddress = new VirtualMta.VirtualMTA() { IPAddress = IPAddress.Parse("127.0.0.1") }; MantaMTA.Core.DNS.MXRecord mxRecord = new MantaMTA.Core.DNS.MXRecord("localhost", 10, uint.MaxValue, DNS.MxRecordSrc.A); SmtpOutboundClient smtpClient = new SmtpOutboundClient(ipAddress); smtpClient.ConnectAsync(mxRecord).Wait(); Assert.IsTrue(smtpClient.Connected); Action sendMessage = new Action(delegate() { Action <string> callback = new Action <string>(delegate(string str) { }); Task.Run(async delegate() { await smtpClient.ExecHeloOrRsetAsync(callback); await smtpClient.ExecMailFromAsync(new System.Net.Mail.MailAddress("testing@localhost"), callback); await smtpClient.ExecRcptToAsync(new System.Net.Mail.MailAddress("postmaster@localhost"), callback); await smtpClient.ExecDataAsync("hello", callback, async(response) => { await Task.Delay(100); }); return(true); }).Wait(); }); sendMessage(); Assert.IsTrue(smtpClient.Connected); sendMessage(); Assert.IsTrue(smtpClient.Connected); sendMessage(); Assert.IsTrue(smtpClient.Connected); sendMessage(); Assert.IsTrue(smtpClient.Connected); sendMessage(); Assert.IsFalse(smtpClient.Connected); } }
public void SmtpClientPoolTest() { using (SmtpServer s = new SmtpServer(25)) { VirtualMta.VirtualMTA ipAddress = new VirtualMta.VirtualMTA() { IPAddress = IPAddress.Parse("127.0.0.1") }; MantaMTA.Core.DNS.MXRecord mxRecord = new MantaMTA.Core.DNS.MXRecord("localhost", 10, uint.MaxValue, DNS.MxRecordSrc.A); SmtpOutboundClient smtpClient = new SmtpOutboundClient(ipAddress); smtpClient.ConnectAsync(mxRecord).Wait(); MantaMTA.Core.Smtp.SmtpClientPool.Instance.Enqueue(smtpClient); smtpClient = MantaMTA.Core.Smtp.SmtpClientPool.Instance.DequeueAsync(ipAddress, new MantaMTA.Core.DNS.MXRecord[] { mxRecord }).Result.SmtpOutboundClient; Assert.NotNull(smtpClient); Assert.IsTrue(smtpClient.Connected); } }
public void SmtpClientIdleTimeout() { if (MessageBox.Show("Do you want to run the idle timeout test?", "Idle timeout test", MessageBoxButtons.YesNo) == DialogResult.Yes) { using (SmtpServer s = new SmtpServer(25)) { VirtualMta.VirtualMTA outboundEndpoint = new VirtualMta.VirtualMTA() { IPAddress = IPAddress.Parse("127.0.0.1") }; MantaMTA.Core.DNS.MXRecord mxRecord = new MantaMTA.Core.DNS.MXRecord("localhost", 10, uint.MaxValue, DNS.MxRecordSrc.A); SmtpOutboundClient smtpClient = new SmtpOutboundClient(outboundEndpoint); smtpClient.ConnectAsync(mxRecord).Wait(); Assert.IsTrue(smtpClient.Connected); System.Threading.Thread.Sleep((MantaMTA.Core.MtaParameters.Client.ConnectionIdleTimeoutInterval + 5) * 1000); Assert.IsFalse(smtpClient.Connected); } } }
/// <summary> /// This method handles failure of delivery. /// Logs failure /// Deletes queued data /// </summary> /// <param name="failMsg"></param> public async Task <bool> HandleDeliveryFailAsync(string failMsg, VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord) { await MtaTransaction.LogTransactionAsync(this, TransactionStatus.Failed, failMsg, ipAddress, mxRecord); try { // Send fails to Manta.Core.Events for (int i = 0; i < base.RcptTo.Length; i++) { EmailProcessingDetails processingInfo = null; Events.EventsManager.Instance.ProcessSmtpResponseMessage(failMsg, base.RcptTo[i], base.InternalSendID, out processingInfo); } } catch (Exception) { } IsHandled = true; return(true); }
/// <summary> /// Attempt to create a new connection using the specified ip address and mx record. /// </summary> /// <returns>A connected outbound client or NULL</returns> public async Task <CreateNewConnectionAsyncResult> CreateNewConnectionAsync(VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord) { SmtpOutboundClient smtpClient = null; // Get the maximum connections to the destination. int maximumConnections = OutboundRules.OutboundRuleManager.GetMaxConnectionsToDestination(ipAddress, mxRecord); lock (this.SyncRoot) { // Get the currently active connections count. int currentConnections = this.InUseConnections.Count; lock (_ConnectionAttemptsInProgressLock) { // If the current connections count + current connection is less than // the maximum connections then we can create a new connection otherwise // we are maxed out so return null. if (maximumConnections <= (currentConnections + _ConnectionAttemptsInProgress)) { return(new CreateNewConnectionAsyncResult(new MaxConnectionsException())); } // Limit the amount of connection attempts or experiance massive delays 30s+ for client.connect() if (_ConnectionAttemptsInProgress >= SmtpClientQueue.MAX_SIMULTANEOUS_CLIENT_CONNECT_ATTEMPTS) { //Logging.Debug("Cannot attempt to create new connection."); return(new CreateNewConnectionAsyncResult(new MaxConnectionsException())); } //Logging.Debug("Attempting to create new connection."); _ConnectionAttemptsInProgress++; } } // Do the actual creating and connecting of the client outside of the lock // so we don't block other threads. try { // Create the new client and make the connection smtpClient = new SmtpOutboundClient(ipAddress); await smtpClient.ConnectAsync(mxRecord); smtpClient.IsActive = true; this.InUseConnections.Add(smtpClient); } catch (Exception ex) { // If something went wrong clear the client so we don't return something odd. if (smtpClient != null) { smtpClient.Close(); smtpClient.Dispose(); smtpClient = null; } if (ex is SocketException) { return(new CreateNewConnectionAsyncResult(ex)); } if (ex is AggregateException && ex.InnerException is System.IO.IOException) { return(new CreateNewConnectionAsyncResult(ex.InnerException)); } } finally { // Reduce the current attempts as were done. _ConnectionAttemptsInProgress--; if (smtpClient != null) { smtpClient.IsActive = false; } } // Return connected client or null. return(new CreateNewConnectionAsyncResult(smtpClient)); }
/// <summary> /// This method handles message deferal. /// Logs deferral /// Fails the message if timed out /// or /// Sets the next rety date time /// </summary> /// <param name="defMsg">The deferal message from the SMTP server.</param> /// <param name="ipAddress">IP Address that send was attempted from.</param> /// <param name="mxRecord">MX Record of the server tried to send too.</param> /// <param name="isServiceUnavailable">If false will backoff the retry, if true will use the MtaParameters.MtaRetryInterval, /// this is needed to reduce the tail when sending as a message could get multiple try again laters and soon be 1h+ before next retry.</param> public void HandleDeliveryDeferral(string defMsg, VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord, bool isServiceUnavailable = false) { HandleDeliveryDeferralAsync(defMsg, ipAddress, mxRecord, isServiceUnavailable).Wait(); }
/// <summary> /// Logs an MTA Transaction to the database. /// </summary> public static async Task <bool> LogTransactionAsync(MtaMessage msg, TransactionStatus status, string svrResponse, VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord) { using (SqlConnection conn = MantaDB.GetSqlConnection()) { SqlCommand cmd = conn.CreateCommand(); cmd.CommandText = @" BEGIN TRANSACTION INSERT INTO man_mta_transaction (mta_msg_id, ip_ipAddress_id, mta_transaction_timestamp, mta_transactionStatus_id, mta_transaction_serverResponse, mta_transaction_serverHostname) VALUES(@msgID, @ipAddressID, GETUTCDATE(), @status, @serverResponse, @serverHostname)"; switch (status) { case TransactionStatus.Discarded: case TransactionStatus.Failed: case TransactionStatus.TimedOut: cmd.CommandText += @"UPDATE man_mta_send SET mta_send_rejected = mta_send_rejected + 1 WHERE mta_send_internalID = @sendInternalID" ; break; case TransactionStatus.Success: cmd.CommandText += @"UPDATE man_mta_send SET mta_send_accepted = mta_send_accepted + 1 WHERE mta_send_internalID = @sendInternalID" ; break; } cmd.CommandText += " COMMIT TRANSACTION"; cmd.Parameters.AddWithValue("@sendInternalID", msg.InternalSendID); cmd.Parameters.AddWithValue("@msgID", msg.ID); if (ipAddress != null) { cmd.Parameters.AddWithValue("@ipAddressID", ipAddress.ID); } else { cmd.Parameters.AddWithValue("@ipAddressID", DBNull.Value); } if (mxRecord != null) { cmd.Parameters.AddWithValue("@serverHostname", mxRecord.Host); } else { cmd.Parameters.AddWithValue("@serverHostname", DBNull.Value); } cmd.Parameters.AddWithValue("@status", (int)status); cmd.Parameters.AddWithValue("@serverResponse", svrResponse); await conn.OpenAsync(); await cmd.ExecuteNonQueryAsync(); return(true); } }
/// <summary> /// Logs an MTA Transaction to the database. /// </summary> public static void LogTransaction(MtaMessage msg, TransactionStatus status, string svrResponse, VirtualMta.VirtualMTA ipAddress, DNS.MXRecord mxRecord) { LogTransactionAsync(msg, status, svrResponse, ipAddress, mxRecord).Wait(); }