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; }