// This is a 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.IO; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Configuration.Install; using System.ComponentModel; 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; // This is a member in case we want to add registry monitoring private Dictionary _RoutingTable; private String OverrideSettingsFileName = Environment.GetEnvironmentVariable("ExchangeInstallPath", EnvironmentVariableTarget.Machine) + @"TransportRoles\agents\Custom\Microsoft.Exchange.SBR.OverrideSettings.config"; private Dictionary _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() { this.eventLogRegKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\services\\MSExchangeSbrAgent\\Diagnostics"); // Read EventLogLevel from Registry 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()) .Select(line => line.Split(';')) .ToDictionary(items => items[0], items => items[1]); // assumes line has 2 elements, no error check _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 >= 4) 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 (eventLogLevel >= 5) EventLog.WriteEntry("Microsoft.Exchange.SBR", "\"X-MS-Exchange-Organization-AuthAs\" header for message " + emailMessageId + " (\"" + emailMessageSubject + "\"):\n" + emailMessageOrgAuthAs.Value.ToString().ToLower(), EventLogEntryType.Information, 1); if (!_RoutingTable.ContainsKey(senderOnlyDomainPart) && !_RoutingTable.ContainsKey(senderFullEmailAddress)) { // sender email domain or address not found in rerouting table, quit if (eventLogLevel >= 4) 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; } else { // sender email domain or address found in rerouting table int rerouteExternalCount = 0; int rerouteInternalCount = 0; int noRerouteCount = 0; int totalCount = 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); foreach (EnvelopeRecipient recp in e.MailItem.Recipients) { if (_InternalDomains.ContainsKey(recp.Address.DomainPart.ToLower())) { // recipient domain is defined internal, now we have to look if GroupID is equal or not if (_InternalDomains[senderOnlyDomainPart].Equals(_InternalDomains[recp.Address.DomainPart.ToLower()], StringComparison.OrdinalIgnoreCase)) { // No routing override, GroupID is equal if (eventLogLevel >= 2) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Not overriding recipient " + recp.Address.ToString() + " on " + emailMessageId + " (\"" + emailMessageSubject + "\"):\nGroupID " + _InternalDomains[senderOnlyDomainPart].ToString().ToLower() + " is equal.", EventLogEntryType.Information, 4); noRerouteCount++; } else { // Routing override, GroupID is not equal if (eventLogLevel >= 2) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Overriding recipient " + recp.Address.ToString() + " on " + emailMessageId + " (\"" + emailMessageSubject + "\"):\nRecipient's GroupID " + _InternalDomains[recp.Address.DomainPart.ToLower()].ToString().ToLower() + " is not equal to sender's GroupID " + _InternalDomains[senderOnlyDomainPart].ToString().ToLower() + ".", EventLogEntryType.Information, 4); RoutingOverride newRoute = new RoutingOverride(myRoutingOverride, DeliveryQueueDomain.UseOverrideDomain); source.SetRoutingOverride(recp, newRoute); rerouteInternalCount++; } } else { // recipient domain is not defined internal, so it must be external and routing overridden... if (eventLogLevel >= 2) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Overriding recipient " + recp.Address.ToString() + " on " + emailMessageId + " (\"" + emailMessageSubject + "\"):\nRecipient not defined internal.", EventLogEntryType.Information, 4); RoutingOverride newRoute = new RoutingOverride(myRoutingOverride, DeliveryQueueDomain.UseOverrideDomain); source.SetRoutingOverride(recp, newRoute); rerouteExternalCount++; } totalCount++; } if (eventLogLevel >= 1) EventLog.WriteEntry("Microsoft.Exchange.SBR", "Statistics for message " + emailMessageId + " (\"" + emailMessageSubject + "\"):\n" + totalCount.ToString() + " recipients checked for rerouting.\n" + rerouteExternalCount.ToString() + " external recipients were rerouted.\n" + rerouteInternalCount.ToString() + " internal recipients were rerouted because GroupIDs were not equal.\n" + noRerouteCount.ToString() + " internal recipients were not rerouted because GroupIDs were equal.", 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); } } } }