internal void OnIncomingError(IncomingMessage message, Exception error) { if (Logger.IsDebugEnabled) { Logger.Error(this.BuildVerboseErrorMessage("INCOMING", message, error)); } else { Logger.Error("INCOMING_ERROR {0}", error.Message); } }
/// <summary> /// To simplify outbound mail sending, SMTP Server allows you to drop new messages into a pickup folder /// You don't need to use SmtpClient or some other SMTP client /// </summary> public void Send(IncomingMessage envelope, string pickupFolder, DirectAddressCollection senders, MDNStandard.NotificationType notificationType) { if (string.IsNullOrEmpty(pickupFolder)) { throw new ArgumentException("value null or empty", "pickupFolder"); } if (senders.IsNullOrEmpty()) { return; } foreach (NotificationMessage notification in this.Produce(envelope, senders.AsMailAddresses(), notificationType)) { string filePath = Path.Combine(pickupFolder, Extensions.CreateUniqueFileName()); notification.Save(filePath); } }
internal void UpdateMdn(IncomingMessage message) { Debug.Assert(m_settings.HasMdnManager); using (MdnMonitorClient client = m_settings.MdnMonitor.CreateMdnMonitorClient()) { var notification = MDNParser.Parse(message.Message); var disposition = notification.Disposition; var originalMessageId = notification.OriginalMessageID; var originalSender = message.Recipients.First().Address; var originalRecipient = message.Sender.Address; client.Update( new Mdn // extract into MdnMonitorParser { MessageId = originalMessageId, Recipient = originalRecipient, Sender = originalSender, Status = disposition.Notification.ToString() }); } }
/// <summary> /// Decrypt (optionally) the given message and try to extract signatures /// </summary> bool DecryptSignatures(IncomingMessage message, X509Certificate2 certificate, out SignedCms signatures, out MimeEntity payload) { MimeEntity decryptedEntity = null; signatures = null; payload = null; if (certificate != null) { decryptedEntity = m_cryptographer.DecryptEntity(message.GetEncryptedBytes(m_cryptographer), certificate); } else { decryptedEntity = message.Message; } if (decryptedEntity == null) { return false; } if (SMIMEStandard.IsContentEnvelopedSignature(decryptedEntity.ParsedContentType)) { signatures = m_cryptographer.DeserializeEnvelopedSignature(decryptedEntity); payload = MimeSerializer.Default.Deserialize<MimeEntity>(signatures.ContentInfo.Content); } else if (SMIMEStandard.IsContentMultipartSignature(decryptedEntity.ParsedContentType)) { SignedEntity signedEntity = SignedEntity.Load(decryptedEntity); signatures = m_cryptographer.DeserializeDetachedSignature(signedEntity); payload = signedEntity.Content; } else { throw new AgentException(AgentError.UnsignedMessage); } return true; }
void ValidateRoutingHeaders(IncomingMessage message) { if (!message.AreAddressesInRoutingHeaders(message.DomainRecipients)) { throw new AgentException(AgentError.RecipientMismatch); } }
int CountNotificationsToBeSent(IncomingMessage incoming) { return m_producer.Produce(incoming).Count(); }
void CollectSignatures(StringBuilder builder, IncomingMessage message) { if (!message.HasSignatures) { return; } SignerInfoCollection allSigners = message.Signatures.SignerInfos; foreach (SignerInfo signer in allSigners) { X509Certificate2 cert = signer.Certificate; string output = string.Format( "E/CN={0},ValidFrom={1},ValidTo={2},IssuerName={3},Version={4},Thumbprint={5}", cert.ExtractEmailNameOrName(), cert.GetEffectiveDateString(), cert.GetExpirationDateString(), cert.Issuer, cert.Version, cert.Thumbprint); builder.Append("CERT:").AppendLine(output); } }
/// <summary> /// Generate external notification messages for failed final destination delivery /// </summary> /// <param name="envelope"></param> /// <param name="recipients">sending failure status for these message recipients</param> /// <returns>An DSNmessage</returns> public DSNMessage ProduceFailure(IncomingMessage envelope, DirectAddressCollection recipients) { if (envelope == null) { throw new ArgumentNullException("envelope"); } if (string.IsNullOrEmpty(m_settings.ProductName)) { throw new ArgumentException("reportingAgentName:AgentSettings:ProductName"); } DSNPerMessage perMessage = new DSNPerMessage(envelope.Sender.Host, envelope.Message.IDValue); // // Un-Deliverable recipients // List<DSNPerRecipient> dsnPerRecipients = envelope.CreatePerRecipientStatus(recipients.AsMailAddresses() , m_settings.Text, m_settings.AlwaysAck, DSNStandard.DSNAction.Failed, DSNStandard.DSNStatus.Permanent , DSNStandard.DSNStatus.DELIVERY_OTHER).ToList(); DSN dsn = new DSN(perMessage, dsnPerRecipients); //Configure and/or dynamic plus refactor //TODO: Split messages by domain. See envelope.Recipients[0] below. return envelope.Message.CreateStatusMessage(new MailAddress("Postmaster@" + envelope.Recipients[0].Host), dsn); }
/// <summary> /// Ensure that domain recipients are KNOWN - i.e. registered with the Config System /// If not, remove them. /// </summary> /// <param name="message"></param> void VerifyDomainRecipientsRegistered(IncomingMessage message) { message.EnsureRecipientsCategorizedByDomain(this.SecurityAgent.Domains); if (!message.HasDomainRecipients) { throw new AgentException(AgentError.NoRecipients); } DirectAddressCollection recipients = message.DomainRecipients; if (this.Settings.MaxIncomingDomainRecipients > 0 && recipients.Count > this.Settings.MaxIncomingDomainRecipients) { throw new AgentException(AgentError.MaxDomainRecipients); } if (!m_settings.HasAddressManager) { // Address validation is turned off return; } Address[] resolved = m_configService.GetAddresses(recipients); if (resolved.IsNullOrEmpty()) { throw new AgentException(AgentError.NoDomainRecipients); } // Remove any addresses that could not be resolved // Yes, this is currently n^2, but given the typical # of addresses, cost should be insignificant int i = 0; while (i < recipients.Count) { DirectAddress recipient = recipients[i]; int iAddress = Array.FindIndex<Address>(resolved, x => x.Match(recipient)); if (iAddress >= 0) { ++i; // Found recipient.Tag = resolved[iAddress]; } else { recipients.RemoveAt(i); } } }
/// <summary> /// Decrypts and verifies trust in a signed and encrypted RFC 5322 formatted message, providing a sender and recipient addresses. /// The provided sender and recipient addresses will be used instead of the header information in the <c>messageText</c>. /// </summary> /// <param name="messageText"> /// An RFC 5322 formatted message string. /// </param> /// <param name="recipients"> /// A <see cref="DirectAddressCollection"/> instance representing recipient addresses. /// </param> /// <param name="sender"> /// An <see cref="DirectAddress"/> instance representing the sender address /// </param> /// <returns> /// An <see cref="IncomingMessage"/> instance with the trust verified decrypted and verified message. /// </returns> public IncomingMessage ProcessIncoming(string messageText, DirectAddressCollection recipients, DirectAddress sender) { IncomingMessage message = new IncomingMessage(messageText, recipients, sender); return this.ProcessIncoming(message); }
/// <summary> /// Generate notification messages (if any) for this source message /// </summary> /// <param name="envelope"></param> /// <returns>An enumeration of notification messages</returns> public IEnumerable<NotificationMessage> Produce(IncomingMessage envelope) { return this.Produce(envelope, envelope.HasDomainRecipients ? envelope.DomainRecipients.AsMailAddresses() : null, MDNStandard.NotificationType.Processed); }
protected virtual void SendNotifications(IncomingMessage envelope, DirectAddressCollection senders) { if (!m_settings.InternalMessage.HasPickupFolder || !m_settings.Notifications.AutoResponse) { return; } // // Its ok if we fail on sending notifications - that should never cause us to not // deliver the message // try { if(envelope.Message.IsMDN() || envelope.Message.IsDSN()) { return; } m_notifications.Send(envelope, m_settings.InternalMessage.PickupFolder, senders, MDNStandard.NotificationType.Processed); if(m_settings.Notifications.GatewayIsDestination && envelope.Message.IsTimelyAndReliable()) { m_notifications.Send(envelope, m_settings.InternalMessage.PickupFolder, senders, MDNStandard.NotificationType.Dispatched); } } catch (Exception ex) { Logger.Error("While sending notification {0}", ex.ToString()); } }
void Notify(IncomingMessage message, Exception ex) { try { Action<IncomingMessage, Exception> errorIncoming = ErrorIncoming; if (errorIncoming != null) { errorIncoming(message, ex); } } catch { } }
void SendDeliveryStatus(IEnumerable<Route> router, IncomingMessage envelope, DirectAddressCollection routedRecipients) { if (envelope.Message.IsMDN() || envelope.Message.IsDSN()) { return; } DirectAddressCollection undeliveredRecipients = new DirectAddressCollection(); foreach (var route in router) { if (route.FailedDelivery) { foreach (var routedRecipient in routedRecipients) { if(route.AddressType == routedRecipient.Tag as string) { undeliveredRecipients.Add(routedRecipient); } } } } try { m_notifications.SendFailure(envelope, m_settings.InternalMessage.PickupFolder, undeliveredRecipients); } catch (Exception ex) { Logger.Error("While sending un-secured DSN {0}", ex.Message); } }
void PostProcessIncoming(ISmtpMessage message, IncomingMessage envelope) { this.CopyMessage(message, m_settings.Incoming); if (envelope.HasDomainRecipients) { DirectAddressCollection routedRecipients = new DirectAddressCollection(); m_router.Route(message, envelope, routedRecipients); this.SendNotifications(envelope, routedRecipients); SendDeliveryStatus(m_router, envelope, routedRecipients); } // // Any recipients that were handled by routes are no longer in the DomainRecipients collection (removed) // Smtp Server should continue process any domain recipients whose delivery were NOT handled by routes // if (m_settings.Incoming.EnableRelay && envelope.HasDomainRecipients) { this.SendNotifications(envelope, envelope.DomainRecipients); // // We only want the incoming message sent to trusted domain recipients // We are not allowing arbitrary relay // message.SetRcptTo(envelope.DomainRecipients); m_diagnostics.LogEnvelopeHeaders(message); } else { // // SMTP Server need not proceed with delivery because we already routed the message to all domain recipients // message.Abort(); } }
private void OnPostProcessIncoming(IncomingMessage message) { if (m_settings.HasMdnManager && message.Message.IsMDN()) { m_monitorService.UpdateMdn(message); } }
//----------------------------- // // Events // //----------------------------- void Notify(IncomingMessage message, Action<IncomingMessage> eventHandler) { // // exceptions are interpreted as: abort message // if (eventHandler != null) { eventHandler(message); } }
/// <summary> /// Decrypts and verifies trust in an IncomingMessage instance with signed and encrypted message content. /// </summary> /// <param name="message"> /// A <see cref="IncomingMessage"/> instance with signed and encrypted content for decryption and trust verification. /// </param> /// <returns> /// An <see cref="IncomingMessage"/> instance with the trust verified decrypted and verified message. /// </returns> public IncomingMessage ProcessIncoming(IncomingMessage message) { if (message == null) { throw new ArgumentNullException("message"); } try { message.Validate(); this.Notify(message, this.PreProcessIncoming); this.ProcessMessage(message); this.Notify(message, this.PostProcessIncoming); } catch (Exception error) { this.Notify(message, error); throw; // rethrow error } return message; }
/// <summary> /// To simplify outbound mail sending, SMTP Server allows you to drop new messages into a pickup folder /// You don't need to use SmtpClient or some other SMTP client /// </summary> public void SendFailure(IncomingMessage envelope, string pickupFolder, DirectAddressCollection recipients) { if (string.IsNullOrEmpty(pickupFolder)) { throw new ArgumentException("value null or empty", "pickupFolder"); } if (recipients.IsNullOrEmpty()) { return; } if (recipients != null) { DSNMessage notification = this.ProduceFailure(envelope, recipients); string filePath = Path.Combine(pickupFolder, Extensions.CreateUniqueFileName()); notification.Save(filePath); } //Or maybe // // m_router.Route(message, envelope, routedRecipients); // // This would avoid loopback encrypt/decrypt... // // ISmtpMessage message // MessageEnvelope envelope // DirectAddressCollection routedRecipients, but would use DSN in-reply-to: // }
public void TestEndToEndTimelyAndReliableStartMdnMonitor() { CleanMessages(m_agent.Settings); m_agent.Settings.InternalMessage.EnableRelay = true; m_agent.Settings.Notifications.AutoResponse = true; m_agent.Settings.Notifications.AlwaysAck = true; m_agent.Settings.MdnMonitor = new ClientSettings(); m_agent.Settings.MdnMonitor.Url = "http://localhost:6692/MonitorService.svc/Dispositions"; // // Process loopback messages. Leaves un-encrypted mdns in pickup folder // Go ahead and pick them up and Process them as if they where being handled // by the SmtpAgent by way of (IIS)SMTP hand off. string textMessage = string.Format(TestMessageTimelyAndReliableMissingTo, Guid.NewGuid()); var sendingMessage = LoadMessage(textMessage); Assert.DoesNotThrow(() => RunEndToEndTest(sendingMessage)); // // grab the clear text mdns and delete others. // bool foundMdns = false; foreach (var pickupMessage in PickupMessages()) { string messageText = File.ReadAllText(pickupMessage); if (messageText.Contains("disposition-notification")) { foundMdns = true; var cdoMessage = LoadMessage(messageText); Assert.DoesNotThrow(() => RunMdnOutBoundProcessingTest(cdoMessage)); } } Assert.True(foundMdns); // // Now the messages are encrypted and can be handled // Processed Mdn's will be recorded by the MdnMonitorService // bool foundFiles = false; foreach (var pickupMessage in PickupMessages()) { foundFiles = true; string messageText = File.ReadAllText(pickupMessage); CDO.Message message = LoadMessage(messageText); Assert.DoesNotThrow(() => RunMdnInBoundProcessingTest(message)); TestMdnsInProcessedStatus(message, true); } Assert.True(foundFiles); // // Prepare a Dispatched MDN manually as if this was a edge client // // // Original message needed to create RequestNotification which is // needed to use the CreateNotificationMessages // var mailMessage = MailParser.ParseMessage(textMessage); mailMessage.RequestNotification(); textMessage = mailMessage.ToString(); var incoming = new IncomingMessage(textMessage); List<NotificationMessage> notificationMessages = GetNotificationMessages(incoming, MDNStandard.NotificationType.Dispatched); Assert.True(notificationMessages.Count == 2); // // Simulating a destination client sending a dispatched MDN // foreach (var notification in notificationMessages) { TestMdnTimelyAndReliableExtensionField(notification, true); var dispatchText = MimeSerializer.Default.Serialize(notification); CDO.Message message = LoadMessage(dispatchText); RunEndToEndTest(message); var duplicateMessage = LoadMessage(dispatchText); // // Prove we cannot send duplicate MDNs. // RunMdnOutBoundProcessingTest(duplicateMessage); VerifyOutgoingMessage(duplicateMessage); //Encryted Message m_agent.ProcessMessage(duplicateMessage); //Decrypts Message //This proves we could not process the message because it is still encrypted //Could possibly check to see if it was dropped. This integration test is getting ugly... VerifyOutgoingMessage(duplicateMessage); //Encryted Message } m_agent.Settings.InternalMessage.EnableRelay = false; }
/// <summary> /// Generate notification messages (if any) for this source message /// </summary> /// <param name="envelope"></param> /// <param name="senders">sending acks on behalf of these message recipients</param> /// <param name="notificationType">processed or dispatched</param> /// <returns>An enumeration of messages</returns> public IEnumerable<NotificationMessage> Produce(IncomingMessage envelope, IEnumerable<MailAddress> senders, MDNStandard.NotificationType notificationType) { if (envelope == null) { throw new ArgumentNullException("envelope"); } if (senders != null && m_settings.AutoResponse) { IEnumerable<NotificationMessage> notifications = envelope.CreateAcks(senders, m_settings.ProductName, m_settings.Text, m_settings.AlwaysAck, notificationType); if (notifications != null) { foreach (NotificationMessage notification in notifications) { yield return notification; } } } }
private List<NotificationMessage> GetNotificationMessages(IncomingMessage incoming, MDNStandard.NotificationType notificationType) { return incoming.Message.CreateNotificationMessages( incoming.Recipients.AsMailAddresses(), sender => Notification.CreateAck ( new ReportingUserAgent ( sender.Host , m_agent.Settings.Notifications.ProductName ) , m_agent.Settings.Notifications.Text , notificationType) ).ToList(); }
void ProcessMessage(IncomingMessage message) { if (message.Sender == null) { throw new AgentException(AgentError.NoSender); } if (!message.HasRecipients) { throw new AgentException(AgentError.NoRecipients); } message.EnsureRecipientsCategorizedByDomain(m_managedDomains); if (!message.HasDomainRecipients) { throw new AgentException(AgentError.NoDomainRecipients); } // // Map each address to its certificates/trust settings // this.BindAddresses(message); // // Decrypt the message, extract the signature and original content // this.DecryptSignedContent(message); // // The standard requires that the original message be wrapped to protect headers // message.Message = this.UnwrapMessage(message.Message); this.ValidateRoutingHeaders(message); // // Enforce trust requirements, including checking signatures // m_trustModel.Enforce(message); // // Remove any untrusted recipients... // if (message.HasDomainRecipients) { message.CategorizeRecipientsByTrust(m_minTrustRequirement); } if (!message.HasDomainRecipients) { throw new AgentException(AgentError.NoTrustedRecipients); } // // Some recipients may not trust this message. Remove them from the To list to prevent accidental message delivery // message.UpdateRoutingHeaders(); }
//--------------------------------------------------- // // Incoming // //--------------------------------------------------- // // Event handler called by the agent // Here, if configured, we will verify that addresses are real. We don't always have to do that, especially if // say we are running as PURELY a gateway. However, if we are set up to e.g. route messages, then... // void OnPreProcessIncoming(IncomingMessage message) { this.VerifyDomainRecipientsRegistered(message); }
public void TestMissingMdn() { CleanMessages(m_agent.Settings); m_agent.Settings.InternalMessage.EnableRelay = true; m_agent.Settings.Notifications.AutoResponse = true; m_agent.Settings.Notifications.AlwaysAck = true; m_agent.Settings.MdnMonitor = new ClientSettings(); m_agent.Settings.MdnMonitor.Url = "http://localhost:6692/MonitorService.svc/Dispositions"; string textMessage = string.Format(TestMessageTimelyAndReliableMissingTo, Guid.NewGuid()); // // RequestNotification needed to use the CreateNotificationMessages // var mailMessage = MailParser.ParseMessage(textMessage); mailMessage.RequestNotification(); textMessage = mailMessage.ToString(); var incoming = new IncomingMessage(textMessage); List<NotificationMessage> notificationMessages = GetNotificationMessages(incoming, MDNStandard.NotificationType.Processed); Assert.True(notificationMessages.Count == 2); RunMdnProcessingForMissingStart(notificationMessages); notificationMessages = GetNotificationMessages(incoming, MDNStandard.NotificationType.Dispatched); Assert.True(notificationMessages.Count == 2); RunMdnProcessingForMissingStart(notificationMessages); notificationMessages = GetNotificationMessages(incoming, MDNStandard.NotificationType.Failed); Assert.True(notificationMessages.Count == 2); RunMdnProcessingForMissingStart(notificationMessages); notificationMessages = GetNotificationMessages(incoming, MDNStandard.NotificationType.Displayed); //No currently using. Assert.True(notificationMessages.Count == 2); RunMdnProcessingForMissingStart(notificationMessages); notificationMessages = GetNotificationMessages(incoming, MDNStandard.NotificationType.Deleted); //No currently using. Assert.True(notificationMessages.Count == 2); RunMdnProcessingForMissingStart(notificationMessages); m_agent.Settings.InternalMessage.EnableRelay = false; }
void BindAddresses(IncomingMessage message) { // // Bind each recpient's certs and trust settings // DirectAddressCollection recipients = message.DomainRecipients; for (int i = 0, count = recipients.Count; i < count; ++i) { DirectAddress recipient = recipients[i]; recipient.Certificates = this.ResolvePrivateCerts(recipient, m_encryptionEnabled); recipient.TrustAnchors = m_trustAnchors.IncomingAnchors.GetCertificates(recipient); } }
internal void ProcessEndToEnd(SmtpAgent agent, Message msg, out OutgoingMessage outgoing, out IncomingMessage incoming) { outgoing = agent.SecurityAgent.ProcessOutgoing(new MessageEnvelope(msg)); incoming = agent.SecurityAgent.ProcessIncoming(new MessageEnvelope(outgoing.SerializeMessage())); }
void DecryptSignedContent(IncomingMessage message) { SignedCms signatures = null; MimeEntity payload = null; bool success = false; if (m_encryptionEnabled) { // // This can be optimized for multiple private keys and recipients where the same certs // are shared across recipients (org certs). But we will start with the easy to understand simple version // // Decrypt and parse message body into a signature entity - the envelope that contains our data + signature // If we fail to decrypt for any recipient, we are going to treat the message as possibly compromised and reject // it entirely // foreach(DirectAddress recipient in message.DomainRecipients) { success = false; signatures = null; payload = null; success = this.DecryptSignedContent(message, recipient, out signatures, out payload); if (!success) { // Any failures.. stop immediately. If we could not decrypt the message for any recipient, // then the message is suspicious and is rejected break; } } } else { success = this.DecryptSignatures(message, null, out signatures, out payload); } if (!success) { throw new AgentException(AgentError.InvalidEncryption); } if (signatures == null || payload == null) { throw new AgentException(AgentError.UntrustedMessage); } message.Signatures = signatures; // // Alter body to contain actual content. Also clean up mime headers on the message that were there to support // signatures etc // HeaderCollection headers = message.Message.Headers; message.Message.Headers = headers.SelectNonMimeHeaders(); message.Message.UpdateBody(payload); // this will merge in content + content specific mime headers }
bool DecryptSignedContent(IncomingMessage message, DirectAddress recipient, out SignedCms signatures, out MimeEntity payload) { signatures = null; payload = null; foreach (X509Certificate2 cert in recipient.Certificates) { try { if (this.DecryptSignatures(message, cert, out signatures, out payload)) { // Decrypted and extracted signatures successfully return true; } } catch(Exception ex) { this.Notify(message, ex); } } return false; }
MessageSignature FindTrustedSignature(IncomingMessage message, MailAddress recipient, X509Certificate2Collection anchors) { DirectAddress sender = message.Sender; MessageSignatureCollection signatures = message.SenderSignatures; MessageSignature lastTrustedSignature = null; foreach (MessageSignature signature in signatures) { bool certTrustedAndInPolicy = (m_certChainValidator.IsTrustedCertificate(signature.Certificate, anchors) && signature.CheckSignature()); if (certTrustedAndInPolicy && recipient != null) { certTrustedAndInPolicy = IsCertPolicyCompliant(recipient, signature.Certificate); } if (certTrustedAndInPolicy) { if (!sender.HasCertificates) { // Can't really check thumbprints etc. So, this is about as good as its going to get return signature; } if (signature.CheckThumbprint(sender)) { return signature; } // // We'll save this guy, but keep looking for a signer whose thumbprint we can verify // If we can't find one, we'll use the last trusted signer we found.. and just mark the recipient's trust // enforcement status as Success_ThumbprintMismatch // lastTrustedSignature = signature; } } return lastTrustedSignature; }