Platform SDK: SMTP Server Events

Sink.cpp

[This is preliminary documentation and subject to change.]

This file contains the implementation for all the exposed CSink interfaces.

// Sink.cpp : Implementation of CSink

#pragma comment(lib,"fcachdll.lib")


#include "stdafx.h"
#include "Archiver.h"
#include "Sink.h"
#include <filehc.h>
#include <mailmsg.h>
#include <mailmsgprops.h>
#include <iostream>
#include <time.h>

#define BUFSIZE 4096

LONG               g_lFileIndex;
CComAutoCriticalSection g_csCritSec;
 
/////////////////////////////////////////////////////////////////////////////
// CSink

STDMETHODIMP CSink::OnMessageSubmission(IMailMsgProperties* pMailMsg,IMailTransportNotify* pINotify,PVOID pvNotifyContext)
{
   HANDLE            hEvent         =   INVALID_HANDLE_VALUE;
   FIO_CONTEXT         *pfiocFile;
   OVERLAPPED         olFileOut;
   HRESULT            hr            = S_OK;
   IMailMsgRecipients* pMsgRecips      = NULL;
   DWORD            dwPropsSize      = 0;
   DWORD            dwWritten      = 0;
   DWORD            dwRecipCount   = 0;
   CHAR            *chBuf         = NULL;
   std::string         strProps;
   std::string         strFileName;
   char tBuf[BUFSIZE];
   HANDLE            hFile         =   INVALID_HANDLE_VALUE;

   chBuf = new char[BUFSIZE];
   memset(chBuf,BUFSIZE,sizeof(char));
   
   InterlockedIncrement(&g_lFileIndex);

   g_csCritSec.Lock();
   strFileName = g_szLogFilePath;
   g_csCritSec.Unlock();

   strFileName += "\\";
   sprintf(tBuf,"arch_%08lx_%08lx.EML",time(NULL),g_lFileIndex);
    strFileName += tBuf;

   hFile = CreateFile( strFileName.c_str(),
                     GENERIC_READ | GENERIC_WRITE,
                     FILE_SHARE_READ,
                     NULL,
                     CREATE_ALWAYS,
                     FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN,
                     NULL);
   if(hFile == INVALID_HANDLE_VALUE)
   {   
      // this is a serious error state at this point
      hr = E_FAIL;
      goto cleanup;
   }

   strProps = "";

   if(SUCCEEDED(hr = pMailMsg->GetStringA(IMMPID_MP_SENDER_ADDRESS_SMTP,BUFSIZE,chBuf) ) ) {   
      strProps += "Sender Address: ";
      strProps += chBuf;
      strProps += "\r\n";
   }
   memset(chBuf,0,BUFSIZE);

   if(SUCCEEDED(hr = pMailMsg->GetStringA(IMMPID_MP_HELO_DOMAIN,BUFSIZE,chBuf) ) ) {   
      strProps += "EHLO Domain from Client: ";
      strProps += chBuf;
      strProps += "\r\n";
   }
   memset(chBuf,0,BUFSIZE);

   if(SUCCEEDED(hr = pMailMsg->GetStringA(IMMPID_MP_CONNECTION_IP_ADDRESS,BUFSIZE,chBuf) ) ) {   
      strProps += "Client IP Address: ";
      strProps += chBuf;
      strProps += "\r\n";
   }
   memset(chBuf,0,BUFSIZE);

   if(SUCCEEDED(hr = pMailMsg->GetStringA(IMMPID_MP_ARRIVAL_TIME,BUFSIZE,chBuf) ) ) {   
      strProps += "Arrival Time: ";
      strProps += chBuf;
      strProps += "\r\n";
   }
   memset(chBuf,0,BUFSIZE);

   if(SUCCEEDED(pMailMsg->QueryInterface(__uuidof(IMailMsgRecipients),(void**)&pMsgRecips))) {
      if(SUCCEEDED(pMsgRecips->Count(&dwRecipCount))) {
         for(DWORD dwCounter = 0;dwCounter<dwRecipCount;dwCounter++) {
            hr = pMsgRecips->GetStringA(dwCounter,IMMPID_RP_ADDRESS_SMTP,4096,chBuf);
            if(SUCCEEDED(hr)) {
               strProps += "Recipient: ";
               strProps += chBuf;
               strProps += "\r\n";
            }
         }
      }
      pMsgRecips->Release();
   }

   strProps += "\r\n";

   hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
   if(hEvent == NULL) {
      hr = E_FAIL;
      CloseHandle(hFile);
      goto cleanup;
   }

   dwPropsSize = (DWORD) strProps.length();
   olFileOut.Offset = 0;
   olFileOut.OffsetHigh = 0;
   olFileOut.hEvent = hEvent;
   if(!WriteFile(hFile,strProps.c_str(),dwPropsSize,&dwWritten,&olFileOut))
   {
      if(GetLastError() == ERROR_IO_PENDING)
         GetOverlappedResult(hFile,&olFileOut,&dwWritten,TRUE);
      else {
         hr = HRESULT_FROM_WIN32(GetLastError());
         CloseHandle(hFile);
         goto cleanup;   
      }
   }
   
   if(  ( pfiocFile = AssociateFile(hFile) ) != NULL) {
      hr = pMailMsg->CopyContentToFileAtOffset(pfiocFile,dwWritten,NULL);
      ReleaseContext(pfiocFile);
   }
   else {
      hr = E_FAIL;
      CloseHandle(hFile);
   }

cleanup:
   if(chBuf != NULL)
      delete [] chBuf;
   if(hEvent != INVALID_HANDLE_VALUE)
      CloseHandle(hEvent);

   return hr;

}
/*
** IPersistPropertyBag : IPersist
*/
STDMETHODIMP CSink::GetClassID(CLSID *pClassID)
{
    return S_OK;
}

STDMETHODIMP CSink::InitNew(void)
{

    return S_OK;
}

STDMETHODIMP CSink::Load(IPropertyBag* pBag,IErrorLog *pErrorLog)
{



    if(pBag == NULL)
        return E_POINTER;

   g_csCritSec.Lock();

   /*
   ** This is a short circuit variable check
   ** here. When there is a binding change, this
   ** instance is destroyed and another object is
   ** created. Since Load is called everytime, no
   ** need to run this code more than once.
   */

    if(g_fHaveLogFileName) {
      g_csCritSec.Unlock();
        return S_OK;
   }

    ATLASSERT(pBag);
    HRESULT hr = S_OK;
    CComVariant varVal;
    hr = pBag->Read(L"LogFilePath",&varVal,pErrorLog);
    if(FAILED(hr)) {
      g_csCritSec.Unlock();
        return S_OK;        // use default archive log file
   }
    /* 
    ** We have a log file path in binding
    */

   /*
   ** Try to enter critical section. If this fails,
   ** another thread is processing the new string
   ** so we just leave.
   */

   UINT uiFilePathLength = SysStringLen(varVal.bstrVal);
   char* temp = NULL;
    temp = new char[uiFilePathLength + 1];
    if(temp == NULL)
       return E_OUTOFMEMORY;    


    if(wcstombs(temp,varVal.bstrVal,uiFilePathLength+1) == NULL) {
       delete [] temp;
       return E_OUTOFMEMORY;
   }

    if(g_szLogFilePath != NULL) 
        delete [] g_szLogFilePath;
    
    g_szLogFilePath = temp;
    g_szLogFilePath[uiFilePathLength] = '\0';
    g_fHaveLogFileName = true;
    
   g_csCritSec.Unlock();

    return S_OK;
   
}


STDMETHODIMP CSink::Save(
                IPropertyBag *pPropBag,
                BOOL fClearDirty,
                BOOL fSaveAllProperties)
{

    return S_OK;
}

/*
** Oddly enough, the most involved code for this example
** is where we register and unregister the event sink bindings.
** What's going on is very simple. We have to add a binding
** to the appropriate source in the SEO database.
** SEO is ole-automation compatible, so much of this code
** mirrors much simpler code using vbscript.
** The hierarchy of SEO objects is as follows:
** 
** EventManager
**  SourceTypes
**    SourceType
**       Sources
**         Source
**           Bindings
**             Binding <--- Adding another of these
**                SinkProperties
**                SourceProperties
** 
*/
STDMETHODIMP CSink::RegisterSink(long lInstance, BSTR DisplayName, BSTR BindingGUID, BSTR LogFilePath, VARIANT_BOOL fEnabled, BSTR* OutBindingGUID)
{

    IEventManager*        pEvtMan       =    NULL;
    IEventUtil*           pEvtUtil      =    NULL;
    IEventSourceTypes*    pSrcTypes     =    NULL;
    IEventSourceType*     pSrcType      =    NULL;
    IEventSources*        pSrcs         =    NULL;
    IEventSource*         pSrc          =    NULL;
    IEventBindingManager* pBindingMan   =    NULL;
    IEventBindings*       pBindings     =    NULL;
    IEventBinding*        pBinding      =    NULL;
    IEventPropertyBag*    pSourceProps  =    NULL;
    IEventPropertyBag*    pSinkProps    =    NULL;
    HRESULT               hr            =    S_OK;
    BSTR                  bstrSourceGUID;

    hr = CoCreateInstance(__uuidof(CEventUtil),
                            NULL,
                            CLSCTX_INPROC_SERVER,
                            __uuidof(IEventUtil),
                            (void**)&pEvtUtil);



    // Get the Source GUID for the SMTP Server Instance
    hr = pEvtUtil->GetIndexedGUID(CComBSTR(g_szGuidSmtpSvcSource),lInstance,&bstrSourceGUID);
    if(FAILED(hr)) {
        pEvtUtil->Release();
        return hr;
    }


    // Use the EventManager to create the binding
    hr = CoCreateInstance(__uuidof(CEventManager),
                            NULL,
                            CLSCTX_INPROC_SERVER,
                            __uuidof(IEventManager),
                            (void**)&pEvtMan);

    if(FAILED(hr)) 
        return hr;


        hr = E_FAIL;
    if(SUCCEEDED(pEvtMan->get_SourceTypes(&pSrcTypes))) {
      if(SUCCEEDED(pSrcTypes->Item(&CComVariant(g_szGuidSmtpSourceType),&pSrcType))) {
        if(SUCCEEDED(pSrcType->get_Sources(&pSrcs))) {    
          if(SUCCEEDED(pSrcs->Item(&CComVariant(bstrSourceGUID),&pSrc))) {
            if(SUCCEEDED(pSrc->GetBindingManager(&pBindingMan))) {
              if(SUCCEEDED(pBindingMan->get_Bindings(CComBSTR(g_szcatidSmtpOnTransportSubmission),&pBindings))) {
                // BindingGUID was passed by the caller
                hr = pBindings->Add(BindingGUID,&pBinding);
                if(SUCCEEDED(hr)) {
                  // error checking is omitted for clarity.
                  // each result _should_ be checked
                  // but these work most of the time
                  pBinding->put_SinkClass(CComBSTR("Archiver.Sink"));
                  pBinding->put_DisplayName(DisplayName);
                  pBinding->put_Enabled(fEnabled);

                  // Source Properties
                  pBinding->get_SourceProperties(&pSourceProps);
                  // Rule is: EHLO command (all)
                  pSourceProps->Add(CComBSTR("Rule"),&CComVariant("mail from=*"));
                  // highest prio
                  pSourceProps->Add(CComBSTR("Priority"),&CComVariant((long) SMTP_TRANSPORT_DEFAULT_PRIORITY));
                  pSourceProps->Release();

                  // Sink Properties
                  pBinding->get_SinkProperties(&pSinkProps);
                  pSinkProps->Add(CComBSTR("LogFilePath"),&CComVariant(LogFilePath));

                  hr = pBinding->Save();
                  // If the caller did not specify a GUID, we return it.
                  // If they did, we return it anyway
                  hr = pBinding->get_ID(OutBindingGUID);
                  pSinkProps->Release();
                  pBinding->Release();
                }
                pBindings->Release();
              }
              pBindingMan->Release();
            }
            pSrc->Release();
          }
          pSrcs->Release();
        }
        pSrcType->Release();
      }
      pSrcTypes->Release();
    }
    pEvtMan->Release();


    ATLASSERT(SUCCEEDED(hr));


    return S_OK;
}


STDMETHODIMP CSink::UnRegisterSink(long lInstance, BSTR BindingGUID)
{

    IEventManager*        pEvtMan      =    NULL;
    IEventUtil*           pEvtUtil     =    NULL;
    IEventSourceTypes*    pSrcTypes    =    NULL;
    IEventSourceType*     pSrcType     =    NULL;
    IEventSources*        pSrcs        =    NULL;
    IEventSource*         pSrc         =    NULL;
    IEventBindingManager* pBindingMan  =    NULL;
    IEventBindings*       pBindings    =    NULL;
    IEventBinding*        pBinding     =    NULL;
    HRESULT               hr           =    S_OK;
    BSTR                  bstrSourceGUID;

    hr = CoCreateInstance(__uuidof(CEventUtil),
                            NULL,
                            CLSCTX_INPROC_SERVER,
                            __uuidof(IEventUtil),
                            (void**)&pEvtUtil);



    // Get the Source GUID for the SMTP Server Instance
    hr = pEvtUtil->GetIndexedGUID(CComBSTR(g_szGuidSmtpSvcSource),lInstance,&bstrSourceGUID);
    if(FAILED(hr)) {
        pEvtUtil->Release();
        return hr;
    }

    pEvtUtil->Release();

    // Use the EventManager to create the binding
    hr = CoCreateInstance(__uuidof(CEventManager),
                            NULL,
                            CLSCTX_INPROC_SERVER,
                            __uuidof(IEventManager),
                            (void**)&pEvtMan);

    if(FAILED(hr)) 
        return hr;

    hr = E_FAIL;
    if(SUCCEEDED(pEvtMan->get_SourceTypes(&pSrcTypes))) {
      if(SUCCEEDED(pSrcTypes->Item(&CComVariant(g_szGuidSmtpSourceType),&pSrcType))) {
        if(SUCCEEDED(pSrcType->get_Sources(&pSrcs))) {    
          if(SUCCEEDED(pSrcs->Item(&CComVariant(bstrSourceGUID),&pSrc))) {
            if(SUCCEEDED(pSrc->GetBindingManager(&pBindingMan))) {
              if(SUCCEEDED(pBindingMan->get_Bindings(CComBSTR(g_szcatidSmtpOnTransportSubmission),&pBindings))) {
                hr = pBindings->Remove(&CComVariant(BindingGUID));
                pBindings->Release();
              }
              pBindingMan->Release();
            }
            pSrc->Release();
          }
          pSrcs->Release();
        }
        pSrcType->Release();
      }
      pSrcTypes->Release();
    }
    pEvtMan->Release();

    return hr;
}