random thoughts, formed in the twisted mind of a coder... RSS 2.0
# Wednesday, 28 January 2009

When writing a custom protocol sink, you might want to use tarpitting for unwanted client behavior. For example, let's say that you want to block some commands on your SMTP server because they're only used (on the internet, not on your local network) by hackers and attackers. TURN is such a command which is normally never used for normal mail transfers. Also HELP is such a command, but could be used by a person wanting to send mail without a mailclient.

 

Note, that if you want to enable tarpitting for every error response on your IIS SMTP or Exchange 2003 (and lower versions), you can use a registry key. Please look at the article "IIS SMTP and Exchange Registry Settings"

 

Ok, how would you create tarpitting just for the TURN command?

You probably want to write a sink, create a method with a Thread.Sleep() and bind it to the TURN command. Right?

 

If you would do it like that, you would block a lot of active connections on your SMTP server. The SMTP server doesn't create a dedicated thread for each client-connection, but rather uses a Threadpool. This means that when you call Thread.Sleep() you would block a thread in the pool. And as that thread was also used to handle other connections, you would now also tarpit other clients.

During heavy load on your server, performance is drastically lower!

 

There are several good ways to do it, but the most elegant way to do it is to use the AsyncCompletion functionality which is provided by the sink interface. It basically comes down to this. Normally when your sink gets called, you run your code and end with writing a response and consume or pass the event. Done.

 

Normally you would consume the event with throwing a COM exception (using the wrappers in C#) like this:

throw new COMException("Event is consumed", ProtocolEventConstants.EXPE_S_CONSUMED);

This ends the sink, consumes the event and sends the response to the client.

If you would omit to consume the event, the response is sent to the next event-hander (another sink or your SMTP server) and so on, but your state is lost and you wouldn't be able to regain the state again.

 

Luckily the sink provides functionality to transfer the state to you. But by using it, you are obliged to end that state yourself and free all resources attached, because otherwise you would create memory leaks.

 

Enough said. Take a look at the example code:

using System;
using System.Runtime.InteropServices;
using Microsoft.Exchange.Transport.EventInterop;
using Microsoft.Exchange.Transport.EventWrappers;

namespace Examples
{
  [Guid("374A6119-A07D-4f14-BDDD-37A91279C5A4")]
  [ComVisible(true)]
  public class TarpittingExample : ISmtpInCommandSink, IEventIsCacheable
  {
    //------------------------------------------------------------------------
    private bool freeContext = true;

    //------------------------------------------------------------------------
    void IEventIsCacheable.IsCacheable()
    {
      //returns S_OK by default
    }

    //------------------------------------------------------------------------
    void ISmtpInCommandSink.OnSmtpInCommand(object _server, object _session, MailMsg _msg, ISmtpInCommandContext _context)
    {
      SmtpInCommandContext context = null;
      
      try
      {
        //wrap the context COM Object
        context = new SmtpInCommandContext(_context);

        //check for which command this event was fired
        if (context.CommandKeyword.ToUpper().Equals("HELP"))
        {
          //- - - - - - - - - - - - - - - 
          //This command is NOT tarpitted
          //- - - - - - - - - - - - - - - 

          //set response
          context.Response        = "500 5.3.3 Unsupported command";
          context.SmtpStatusCode  = 500;
          context.CommandStatus   = (uint)ProtocolEventConstants.EXPE_COMPLETE_FAILURE;

          //consume the event and send the response to the client without tarpitting
          throw new COMException("Event is consumed", ProtocolEventConstants.EXPE_S_CONSUMED);
        }
        else if (context.CommandKeyword.ToUpper().Equals("TURN"))
        {
          //- - - - - - - - - - - - - - - 
          //This command is tarpitted
          //- - - - - - - - - - - - - - - 

          //pre-set response (it's not send until the event is completed)
          context.Response        = "500 5.3.3 Unsupported command";
          context.SmtpStatusCode  = 500;
          context.CommandStatus   = (uint)ProtocolEventConstants.EXPE_COMPLETE_FAILURE;

          //start the async timer, configure it for 10 seconds
          new TarpittingState(this, 10000, _context);  //<<< The COM object is passed, not the wrapper!!!

          //configure event for async handling
          throw new COMException("Event is pending for async completion", ProtocolEventConstants.MAILTRANSPORT_S_PENDING);
        }
      }
      finally
      {
        //release COM objects
        if (_server != null)
          Marshal.ReleaseComObject(_server);
        if (_session != null)
          Marshal.ReleaseComObject(_session);
        if (_msg != null)
          Marshal.ReleaseComObject(_msg);
        if (freeContext && _context != null)  //<<< Not freed when tarpitting!
          Marshal.ReleaseComObject(_context);
      }
    }

    //------------------------------------------------------------------------
    private class TarpittingState
    {
      public Timer                  timer;
      public ISmtpInCommandContext  context;  //<<< This is the COM object, not the wrapper!!!
      public TarpittingExample      caller;

      public TarpittingState(TarpittingExample _caller, int _timeToWait, ISmtpInCommandContext _context)
      {
        //copy props
        caller  = _caller;
        context = _context;
        
        //start timer
        caller.freeContext = false;
        timer = new Timer(timerCallback, this, Config.fields.SMTPRELAY_ABUSE_TARPITTIME, Timeout.Infinite);
      }

      public void timerCallback(object _state)
      {
        //end async state
        context.NotifyAsyncCompletion(ProtocolEventConstants.EXPE_S_CONSUMED);
        
        //free the context COM Object
        caller.freeContext = true;
        Marshal.ReleaseComObject(context);
      }
    }

    //------------------------------------------------------------------------
  }
}

Please note that in the case of tarpitting, the context COM Object is freed by the TarpittingState class and not by the TarpittingExample class!

 

Thanks go out to JoostJan for his help on this subject.

 

For more information on building sinks, please read the article "Creating a custom authentication sink for IIS SMTP or Exchange"

 

Wednesday, 28 January 2009 10:12:23 (W. Europe Standard Time, UTC+01:00)  #    Comments [0]
.Net | Microsoft SMTP | Security
All comments require the approval of the site owner before being displayed.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2017
Martijn Thie
Sign In
Statistics
Total Posts: 18
This Year: 0
This Month: 0
This Week: 0
Comments: 176
All Content © 2017, Martijn Thie
DasBlog theme adapted from 'Business' (originally by delarou)