| Platform SDK: SMTP Server Events |
[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;
}