// This is an Exchange 2013 custom transport agent for sender based re-routing, created by Martin Tuescher // As it is here, it needs a Send Connector configured to route mails to override domains that has equal or // lower costs than the Default Send Connector for "*". using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration.Install; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using Microsoft.Win32; using Microsoft.Exchange.Data.Transport; using Microsoft.Exchange.Data.Transport.Email; using Microsoft.Exchange.Data.Transport.Smtp; using Microsoft.Exchange.Data.Transport.Routing; using Microsoft.Exchange.Data.Common; namespace Microsoft.Exchange.SBR { /// // Run when installing the assembly, this function sets up the required application event logging registry keys /// [RunInstaller(true)] public class SbrRoutingAgentEventLogInstaller : Installer { private EventLogInstaller SbrEventLogInstaller; public SbrRoutingAgentEventLogInstaller() { SbrEventLogInstaller = new EventLogInstaller(); // Create an instance of an EventLogInstaller. SbrEventLogInstaller.Source = "Microsoft.Exchange.SBR"; // Set the source name of the event log. SbrEventLogInstaller.Log = "Application"; // Set the event log that the source writes entries to. Installers.Add(SbrEventLogInstaller); // Add routingeventLogInstaller to the Installer collection. RegistryKey newRegKey = Registry.LocalMachine.CreateSubKey("System\\CurrentControlSet\\Services\\MSExchangeSbrAgent\\Diagnostics"); newRegKey.SetValue("General", 0); } } public sealed class SbrRoutingAgentFactory : RoutingAgentFactory { public override RoutingAgent CreateAgent(SmtpServer server) { RoutingAgent myAgent = new OwnRoutingAgent(); return myAgent; } } public class OwnRoutingAgent : RoutingAgent { private int eventLogLevel = -1; // Don't log if the proper keys aren't setup because this could cause problems later. private RegistryKey eventLogRegKey; private Dictionary _RoutingTable; private String OverrideSettingsFileName = Environment.GetEnvironmentVariable("ExchangeInstallPath", EnvironmentVariableTarget.Machine) + @"TransportRoles\agents\Custom\Microsoft.Exchange.SBR.OverrideSettings.config"; private String[] _InternalDomains; private String InternalDomainsFileName = Environment.GetEnvironmentVariable("ExchangeInstallPath", EnvironmentVariableTarget.Machine) + @"TransportRoles\agents\Custom\Microsoft.Exchange.SBR.InternalDomains.config"; private String[] _IgnoreAuthAsValues; private String IgnoreAuthAsFileName = Environment.GetEnvironmentVariable("ExchangeInstallPath", EnvironmentVariableTarget.Machine) + @"TransportRoles\agents\Custom\Microsoft.Exchange.SBR.IgnoreAuthAs.config"; public OwnRoutingAgent() { // Read EventLogLevel from Registry this.eventLogRegKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\services\\MSExchangeSbrAgent\\Diagnostics"); if (this.eventLogRegKey != null) { if (eventLogRegKey.GetValue("General") != null) { this.eventLogLevel = ((int)eventLogRegKey.GetValue("General")); if (eventLogLevel >= 6) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Event logging set to level " + this.eventLogLevel.ToString() + "."); } } //subscribe to different events base.OnResolvedMessage += new ResolvedMessageEventHandler(OwnRoutingAgent_OnResolvedMessage); _IgnoreAuthAsValues = File .ReadAllLines(IgnoreAuthAsFileName) .Where(line => !String.IsNullOrEmpty(line)) .Where(line => !line.StartsWith("#")) .Select(line => line.ToLower()) .ToArray(); // assumes line has 1 element, no error check _InternalDomains = File .ReadAllLines(InternalDomainsFileName) .Where(line => !String.IsNullOrEmpty(line)) .Where(line => !line.StartsWith("#")) .Select(line => line.ToLower()) .ToArray(); _RoutingTable = File .ReadAllLines(OverrideSettingsFileName) .Where(line => !String.IsNullOrEmpty(line)) .Where(line => !line.StartsWith("#")) .Select(line => line.ToLower()) .Select(line => line.Split(';')) .ToDictionary(items => items[0], items => items[1]); // assumes line has 2 elements, no error check } private void OwnRoutingAgent_OnResolvedMessage(ResolvedMessageEventSource source, QueuedMessageEventArgs e) { try { var routingOverrideDomainPart = ""; var senderFullEmailAddress = e.MailItem.FromAddress.ToString().ToLower(); // get full sender email address var senderOnlyDomainPart = e.MailItem.FromAddress.DomainPart.ToLower(); // get domain part of sender email address var emailMessageId = e.MailItem.Message.MessageId.ToString(); var emailMessageSubject = e.MailItem.Message.Subject.ToString(); Microsoft.Exchange.Data.Mime.Header emailMessageOrgAuthAs; emailMessageOrgAuthAs = e.MailItem.Message.MimeDocument.RootPart.Headers.FindFirst("X-MS-Exchange-Organization-AuthAs"); if (eventLogLevel >= 5) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Entering OnResolved for message " + emailMessageId + " (\"" + emailMessageSubject + "\").", EventLogEntryType.Information, 1); if (!_IgnoreAuthAsValues.Contains(emailMessageOrgAuthAs.Value.ToString().ToLower())) { // X-MS-Exchange-Organization-AuthAs value not found in ignore table (typically "Internal") so we start processing the mail if (!_RoutingTable.ContainsKey(senderOnlyDomainPart) && !_RoutingTable.ContainsKey(senderFullEmailAddress)) { // Nothing to reroute, quit if (eventLogLevel >= 5) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Exiting OnResolved for message " + emailMessageId + " (\"" + emailMessageSubject + "\"):\nSender email domain or address not found in rerouting table.\nSBR agent is not processing this mail.", EventLogEntryType.Information, 8); return; } int rerouteCount = 0; if (_RoutingTable.ContainsKey(senderOnlyDomainPart)) { // sender email domain is in rerouting table if (eventLogLevel >= 3) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Sender email domain " + senderOnlyDomainPart + " for message " + emailMessageId + " (\"" + emailMessageSubject + "\") is in rerouting table.", EventLogEntryType.Information, 2); routingOverrideDomainPart = _RoutingTable[senderOnlyDomainPart]; } if (_RoutingTable.ContainsKey(senderFullEmailAddress)) { // sender email address is in rerouting table if (eventLogLevel >= 3) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Sender email address " + senderFullEmailAddress + " for message " + emailMessageId + " (\"" + emailMessageSubject + "\") is in rerouting table.", EventLogEntryType.Information, 2); routingOverrideDomainPart = _RoutingTable[senderFullEmailAddress]; } var myRoutingOverride = new RoutingDomain(routingOverrideDomainPart); var recipientsToOverride = e.MailItem .Recipients .Where(rec => !_InternalDomains.Contains(rec.Address.DomainPart.ToLower())); foreach (EnvelopeRecipient recp in recipientsToOverride) { if (eventLogLevel >= 2) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Overriding recipient " + recp.Address.ToString() + " on " + emailMessageId + " (\"" + emailMessageSubject + "\").", EventLogEntryType.Information, 4); RoutingOverride newRoute = new RoutingOverride(myRoutingOverride, DeliveryQueueDomain.UseOverrideDomain); source.SetRoutingOverride(recp, newRoute); rerouteCount++; } if (eventLogLevel >= 1) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Statistics for message " + emailMessageId + " (\"" + emailMessageSubject + "\"):\n" + rerouteCount.ToString() + " recipients were rerouted.", EventLogEntryType.Information, 5); if (eventLogLevel >= 4) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Exiting OnResolved for message " + emailMessageId + " (\"" + emailMessageSubject + "\").", EventLogEntryType.Information, 8); } else { // X-MS-Exchange-Organization-AuthAs value found in ignore table if (eventLogLevel >= 4) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Exiting OnResolved for message " + emailMessageId + " (\"" + emailMessageSubject + "\"):\nX-MS-Exchange-Organization-AuthAs header \"" + emailMessageOrgAuthAs.Value.ToString().ToLower() + "\" found in ignore table.\nSBR agent is not processing this mail.", EventLogEntryType.Information, 8); return; } } catch (Exception except) { if (eventLogLevel >= 3) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Exception (OnResolved): " + except.Message + "\n" + except.StackTrace, EventLogEntryType.Information, 10); } } } }