Skip to main content

C# Catchall Onarrival Event sink

One of the Event sinks that comes in handy from time to time especially if people own a lot of domains is the catch all event sink from http://support.microsoft.com/?kbid=324021 .Now the .NET frameworks a bit more prevalent and the fact I wanted to use this on my Internal servers for something I thought I’d give converting this sink to C# ago. There are two ways you can go about writing SMTP event sinks in managed code the first is to build some wrappers as outlined in on msdn. This gives you access to all the protocol and transport events. The second way to build a SMTP event sink in managed code is to use the CDO onarrival event whose interfaces are defined in the cdoex.dll file (or cdosys.dll if you don’t have Exchange). The downside of using the CDO interface is that it adds significant overhead and is synchronous but the upside is that it that it handles most of the parsing and MIME issues. There’s a good doc here that discusses the issues . But using C# is a step up from VBS and should avoid all those nasty STA issues discussed here.

For the code itself its mostly based on the code from the KB with one major exception. I’ve added a section that checks and sets an X-header on the message if it’s processed by the sink. I did this to make sure the sink wouldn’t run multiple times on a message which was in response to an issue that I had with this sink (as well as the original script) in my environment. What happened for me was that I was a little lazy when I registered the sink and instead of just registering it to run on messages being sent to my catch all domain I registered it to run on all messages that where being processed by the server. This was fine for all normal message traffic that flowed though the server but a problem arose when I had some messages that where bound for a mail enabled public folders. I have a front-backend setup and I have a public store mounted on my front end server which contains the folder hierarchy. So when my front end server received the message bound from a mail-enabled public folder it would deliver it locally first as per its logic and then it would resubmit it once it work out where a replica for that folder existed ref . When the event sink ran on this resubmitted message even though it wasn’t making any changes to it the code still goes though the process of writing the recipient list back to the envelope field and calls datasource.save to update the message. Something was happening within this process which would then cause a message loop on my front-backend servers which would just continually bounce the message between each of the servers until I removed the sink. This may mean I have a problem somewhere else and it wouldn’t have happened if I had bound the sink correctly in the first place but it was enough to prompt me to change this sink to prevent this type of thing happening In the future. The one draw back of adding an X-header was that it invalidates any digital signatures but for a catch domain this isn’t a big deal.

Down to the coding

The first thing to do is create a new classlibrary project in visual studio

To create the sink you need to grab 3 dll’s from your server the first is the codex.dll you also should grab the seo.dll from %windir%\system32\inetsrv and the last dll you need is the PIA for ado. I used the PIA from OWA on Exchange 2003 which is the Adodb.dll file in the Exchsrvr\OMA\browse\bin directory. This is one thing you need to be careful of as there are a few PIA’s kicking around which are different versions. Before you use it you may want to list to see if any are registered in the GAC by using gacutil –l. Usually there isn’t but I had a problem on one server where I had a version of ADODB registered in the GAC which was a different version then the PIA I was trying to use which caused a muck of problems.

Once you have all the DLL’s you need to create strong named assemblies for codex and seo so you need to first create a keypair with sn.exe eg sn.exe –k :SMTPOnarrival.key

Then using Tlbimp.exe build some Interop dll’s I’ve used the namespace switch to make sure CDOEX gets assigned CDO for the namespace eg

tlbimp cdoex.dll /namespace:CDO /keyfile:SMTPOnarrival.key /out:Interop.cdoex.dll

tlbimp seo.dll /namespace:SEO /keyfile:SMTPOnarrival.key /out:Interop.seo.dll

Once you’ve done this you can then reference all three dll’s in your project and you also need to add the keypair name to AssemblyKeyFile property in the assembly info.

The other thing you need to do is in the project properties-configuration properties you need to make sure that Regsiter for Com interop is set to true

Add the code then make sure you add a new unique GUID using Tools – Create GUID (create registry format). You need to change the catch domain and replace mailbox in the code which are hard coded as well you should set a unique x-header for the server.

Once you’ve done this you need to register your dll using regasm with the /codebase switch eg regasm onarrivalesink.dll /codebase

And then finally bind your sink using SMTPreg.vbs (which comes with the Exchange SDK there is also a copy in http://support.microsoft.com/?kbid=324021). When your binding it I would make sure you bind it so it only fires on emails sent to your catch domain recipients so a registration like

cscript smtpreg.vbs /add 1 onarrival CatchallSink SMTPonarrival.Catchall "rcpt to=*@youdomain.com"

If you want to debug your code (which you should only be doing on a dev server) because SMTP event sinks run in-process (of IIS) within Visual Studio to debug you need to select tools – debug process and then attach to the inetinfo.exe process (for CLR). The only quirk that I found was that Inetinfo needs to have successfully loaded your code to allow you to connect the debugger (eg the sink needs to have fired once first) or you just can’t connect. The other small quirk that I haven’t worked out yet is that I had to keep restarting the IISadmin service (and dependants) to make it release the DLL so I could make changes.

The code I’ve used is very low on testing so I wouldn’t trust it in anything other then a test environment.

I’ve put a downloadable copy of the code here

The code itself looks like

using System;
using System.Runtime.InteropServices;
using CDO;
using ADODB;
using SEO;

namespace SMTPonarrival
{
[Guid("E045FD54-4E2D-4a8e-8431-FF351F98B14A")]
public class Catchall : ISMTPOnArrival , IEventIsCacheable
{
void ISMTPOnArrival.OnArrival(IMessage msg, ref CdoEventStatus EventStatus)
{
try
{
if (msg.Fields["urn:schemas:mailheader:X-catchall"].Value == null)
{
ProcessMessage(msg);
};
}
catch(Exception e)
{
System.IO.StreamWriter logfile = new System.IO.StreamWriter("c:\\SMTPEventerrorlog.txt",true);
logfile.WriteLine("Sink Fired : " + System.DateTime.Now);
logfile.WriteLine("Error : " + e.Message);
logfile.Close();
}
//Set Event Status to CDO_RUN_NEXT_SINK
EventStatus = CDO.CdoEventStatus.cdoRunNextSink;
}
void IEventIsCacheable.IsCacheable()
{
// This will return S_OK by default.
}

private void ProcessMessage(IMessage msg1)
{
string RECIPLIST;
RECIPLIST = "http://schemas.microsoft.com/cdo/smtpenvelope/recipientlist";
string strFixedListlc;
string searchdomain = "@catchdomain.com";
string strreplaceaddr = "SMTP:catchmailbox@yourdomain.com;";
string strFixedList = msg1.EnvelopeFields[RECIPLIST].Value.ToString();
while (strFixedList.IndexOf(searchdomain ,1) != -1 )
{
strFixedListlc = strFixedList.ToLower();
int nDomainPart = strFixedListlc.IndexOf(searchdomain,1);
int nNamePart = strFixedList.LastIndexOf(";",nDomainPart);
int nNextAddress = strFixedList.IndexOf("SMTP:",nDomainPart);
if (nNamePart == -1)
{
if (nNextAddress == -1)
{
strFixedList = strreplaceaddr;}
else
{
strFixedList = strreplaceaddr + strFixedList.Remove(0,nNextAddress);}
}
else
{
if (nNextAddress == -1)
{
strFixedList = strFixedList.Remove(nNamePart,strFixedList.Length-nNamePart) + ";" + strreplaceaddr;}
else
{
strFixedList = strFixedList.Remove(nNamePart,strFixedList.Length-nNamePart) + ";" + strreplaceaddr + strFixedList.Remove(0,nNextAddress);
}
}
}
msg1.EnvelopeFields[RECIPLIST].Value = strFixedList;
msg1.EnvelopeFields.Update();
msg1.Fields["urn:schemas:mailheader:X-catchall"].Value = "Server-CatchALL";
msg1.Fields.Update();
msg1.DataSource.Save();
}
}
}

Popular posts from this blog

Downloading a shared file from Onedrive for business using Powershell

I thought I'd quickly share this script I came up with to download a file that was shared using One Drive for Business (which is SharePoint under the covers) with Powershell. The following script takes a OneDrive for business URL which would look like https://mydom-my.sharepoint.com/personal/gscales_domain_com/Documents/Email%20attachments/filename.txt This script is pretty simple it uses the SharePoint CSOM (Client side object Model) which it loads in the first line. It uses the URI object to separate the host and relative URL which the CSOM requires and also the SharePointOnlineCredentials object to handle the Office365 SharePoint online authentication. The following script is a function that take the OneDrive URL, Credentials for Office365 and path you want to download the file to and downloads the file. eg to run the script you would use something like ./spdownload.ps1 ' https://mydom-my.sharepoint.com/personal/gscales_domain_com/Documents/Email%20attachments/filena

A walk-though using the Graph API Mailbox reports in Powershell

Quite recently the Reporting side of the Graph API has moved in GA from beta, there are quite a number of reports that can be run across various Office365 surfaces but in this post I'm going to focus on the Mailbox related ones. Accessing Office365 Reports using Powershell is nothing new and has been available in the previous reporting endpoint  https://msdn.microsoft.com/en-us/library/office/jj984326.aspx however from the end of January many of these cmdlets are now being depreciated in favour of the Graph API  https://msdn.microsoft.com/en-us/library/office/dn387059.aspx . Prerequisites  In comparison to using the Remote PowerShell cmdlets where only the correct Office365 Admin permissions where needed, to use the new Graph API reports endpoint you need to use OAuth for authentication so this requires an Application Registration  https://developer.microsoft.com/en-us/graph/docs/concepts/auth_overview  that is then given the correct oAuth Grants to use the Reports EndPoin

How to test SMTP using Opportunistic TLS with Powershell and grab the public certificate a SMTP server is using

Most email services these day employ Opportunistic TLS when trying to send Messages which means that wherever possible the Messages will be encrypted rather then the plain text legacy of SMTP.  This method was defined in RFC 3207 "SMTP Service Extension for Secure SMTP over Transport Layer Security" and  there's a quite a good explanation of Opportunistic TLS on Wikipedia  https://en.wikipedia.org/wiki/Opportunistic_TLS .  This is used for both Server to Server (eg MTA to MTA) and Client to server (Eg a Message client like Outlook which acts as a MSA) the later being generally Authenticated. Basically it allows you to have a normal plain text SMTP conversation that is then upgraded to TLS using the STARTTLS verb. Not all servers will support this verb so if its not supported then a message is just sent as Plain text. TLS relies on PKI certificates and the administrative issue s that come around certificate management like expired certificates which is why I wrote th
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.