//----------------------------------------------------------------------------- // // // File: dsnsink.cpp // // Description: Implementation of default DSN Generation sink // // Author: Mike Swafford (MikeSwa) // // History: // 6/30/98 - MikeSwa Created // // Copyright (C) 1998 Microsoft Corporation // //----------------------------------------------------------------------------- #include "precomp.h" #ifdef PLATINUM #include "ptntdefs.h" #endif //PLATINUM // // This length is inspired by the other protocols that we deal with. The // default address limit is 1024, but the MTA can allow 1024 + 834 for the // OR address. We'll define out default buffer size to allow this large // of an address. // #define PROP_BUFFER_SIZE 1860 #ifdef DEBUG #define DEBUG_DO_IT(x) x #else #define DEBUG_DO_IT(x) #endif //DEBUG //min sizes for valid status strings #define MIN_CHAR_FOR_VALID_RFC2034 10 #define MIN_CHAR_FOR_VALID_RFC821 3 #define MAX_RFC822_DATE_SIZE 35 void FileTimeToLocalRFC822Date(const FILETIME & ft, char achReturn[MAX_RFC822_DATE_SIZE]); static char *s_rgszMonth[ 12 ] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", }; static char *s_rgszWeekDays[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; #define MAX_RFC_DOMAIN_SIZE 64 //String used in generation of MsgID static char g_szBoundaryChars [] = "0123456789abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static LONG g_cDSNMsgID = 0; //Address types to check for, and their corresponding address types. const DWORD g_rgdwSenderPropIDs[] = { IMMPID_MP_SENDER_ADDRESS_SMTP, IMMPID_MP_SENDER_ADDRESS_X400, IMMPID_MP_SENDER_ADDRESS_LEGACY_EX_DN, IMMPID_MP_SENDER_ADDRESS_X500, IMMPID_MP_SENDER_ADDRESS_OTHER}; const DWORD g_rgdwRecipPropIDs[] = { IMMPID_RP_ADDRESS_SMTP, IMMPID_RP_ADDRESS_X400, IMMPID_RP_LEGACY_EX_DN, IMMPID_RP_ADDRESS_X500, IMMPID_RP_ADDRESS_OTHER}; const DWORD NUM_DSN_ADDRESS_PROPERTIES = 5; const CHAR *g_rgszAddressTypes[] = { "rfc822", "x-x400", "x-ex", "x-x500", "unknown"}; //---[ fLanguageAvailable ]---------------------------------------------------- // // // Description: // Checks to see if resources for a given language are available. // Parameters: // LangId Language to check for // Returns: // TRUE If localized resources for requested language are available // FALSE If resources for that language are not available. // History: // 10/26/98 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL fLanguageAvailable(LANGID LangId) { TraceFunctEnterEx((LPARAM) LangId, "fLanguageAvailable"); HINSTANCE hModule = GetModuleHandle(DSN_RESOUCE_MODULE_NAME); HRSRC hResInfo = NULL; BOOL fResult = FALSE; if (NULL == hModule) { _ASSERT( 0 && "Cannot get resource module handle"); return FALSE; } //Find handle to string table segment hResInfo = FindResourceEx(hModule, RT_STRING, MAKEINTRESOURCE(((WORD)((USHORT)GENERAL_SUBJECT >> 4) + 1)), LangId); if (NULL != hResInfo) fResult = TRUE; else ErrorTrace((LPARAM) LangId, "Unable to load DSN resources for language"); TraceFunctLeave(); return fResult; } //---[ fIsValidMIMEBoundaryChar ]---------------------------------------------- // // // Description: // // Checks to see if the given character is a valid as described by the // RFC2046 BNF for MIME Boundaries: // boundary := 0*69 bcharsnospace // bchars := bcharsnospace / " " // bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / // "+" / "_" / "," / "-" / "." / // "/" / ":" / "=" / "?" // Parameters: // ch Char to check // Returns: // TRUE if VALID // FALSE otherwise // History: // 7/6/98 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL fIsValidMIMEBoundaryChar(CHAR ch) { if (isalnum(ch)) return TRUE; //check to see if it is one of the special case characters if (('\'' == ch) || ('(' == ch) || (')' == ch) || ('+' == ch) || ('_' == ch) || (',' == ch) || ('_' == ch) || ('.' == ch) || ('/' == ch) || (':' == ch) || ('=' == ch) || ('?' == ch)) return TRUE; else return FALSE; } //---[ GenerateDSNMsgID ]------------------------------------------------------ // // // Description: // Generates a unique MsgID string // // The format is: // @ // Parameters: // IN szDomain Domain to generate MsgID for // IN cbDomain Domain to generate MsgID for // IN OUT szBuffer Buffer to write MsgID in // IN cbBuffer Size of buffer to write MsgID in // Returns: // TRUE on success // FALSE otherwise // History: // 3/2/99 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL fGenerateDSNMsgID(LPSTR szDomain,DWORD cbDomain, LPSTR szBuffer, DWORD cbBuffer) { TraceFunctEnterEx((LPARAM) NULL, "fGenerateDSNMsgID"); _ASSERT(szDomain); _ASSERT(cbDomain); _ASSERT(szBuffer); _ASSERT(cbBuffer); // insert the leading < if (cbBuffer >= 1) { *szBuffer = '<'; szBuffer++; cbBuffer--; } const CHAR szSampleFormat[] = "00000000@"; // sample format string const DWORD cbMsgIdLen = 20; //default size of random string LPSTR szStop = szBuffer + cbMsgIdLen; LPSTR szCurrent = szBuffer; DWORD cbCurrent = 0; //minimize size for *internal* static buffer _ASSERT(cbBuffer > MAX_RFC_DOMAIN_SIZE + cbMsgIdLen); if (!szDomain || !cbDomain || !szBuffer || !cbBuffer || (cbBuffer <= MAX_RFC_DOMAIN_SIZE + cbMsgIdLen)) return FALSE; //We want to figure how much room we have for random characters //We will need to fit the domain name, the '@', and the 8 character unique //number // awetmore - add 1 for the trailing > if(cbBuffer < cbDomain + cbMsgIdLen + 1) { //Fall through an allow for 20 characaters and part of domain name //We want to catch this in debug builds _ASSERT(0 && "Buffer too small for MsgID"); } //this should have been caught in parameter checking _ASSERT(cbBuffer > cbMsgIdLen); szStop -= (sizeof(szSampleFormat) + 1); while (szCurrent < szStop) { *szCurrent = g_szBoundaryChars[rand() % (sizeof(g_szBoundaryChars) - 1)]; szCurrent++; } //Add unique number cbCurrent = sprintf(szCurrent, "%8.8x@", InterlockedIncrement(&g_cDSNMsgID)); _ASSERT(sizeof(szSampleFormat) - 1 == cbCurrent); //Figure out how much room we have and add domain name szCurrent += cbCurrent; cbCurrent = (DWORD) (szCurrent-szBuffer); //unless I've messed up the logic this is always true _ASSERT(cbCurrent < cbBuffer); //Add domain part to message id strncat(szCurrent-1, szDomain, cbBuffer - cbCurrent - 1); _ASSERT(cbCurrent + cbDomain < cbBuffer); // Add the trailing >. we accounted for the space above check for // cbBuffer size strncat(szCurrent, ">", cbBuffer - cbCurrent - cbDomain - 1); DebugTrace((LPARAM) NULL, "Generating DSN Message ID %s", szCurrent); TraceFunctLeave(); return TRUE; } #ifdef DEBUG #define _ASSERT_RECIP_FLAGS AssertRecipFlagsFn #define _VERIFY_MARKED_RECIPS(a, b, c) VerifyMarkedRecips(a, b, c) #define _ASSERT_MIME_BOUNDARY(szMimeBoundary) AssertMimeBoundary(szMimeBoundary) //---[ AssertRecipFlagsFn ]---------------------------------------------------- // // // Description: // ***DEBUG ONLY*** // Asserts that the recipient flags defined in mailmsgprops.h are correct // Parameters: // - // Returns: // - // History: // 7/2/98 - MikeSwa Created // //----------------------------------------------------------------------------- void AssertRecipFlagsFn() { DWORD i, j; DWORD rgdwFlags[] = {RP_DSN_NOTIFY_SUCCESS, RP_DSN_NOTIFY_FAILURE, RP_DSN_NOTIFY_DELAY, RP_DSN_NOTIFY_NEVER, RP_DELIVERED, RP_DSN_SENT_NDR, RP_FAILED, RP_UNRESOLVED, RP_EXPANDED, RP_DSN_SENT_DELAYED, RP_DSN_SENT_EXPANDED, RP_DSN_SENT_RELAYED, RP_DSN_SENT_DELIVERED, RP_REMOTE_MTA_NO_DSN, RP_ERROR_CONTEXT_STORE, RP_ERROR_CONTEXT_CAT, RP_ERROR_CONTEXT_MTA}; DWORD cFlags = sizeof(rgdwFlags)/sizeof(DWORD); for (i = 0; i < cFlags;i ++) { for (j = i+1; j < cFlags; j++) { //make sure all have some unique bits if (rgdwFlags[i] & rgdwFlags[j]) { _ASSERT((rgdwFlags[i] & rgdwFlags[j]) != rgdwFlags[j]); _ASSERT((rgdwFlags[i] & rgdwFlags[j]) != rgdwFlags[i]); } } } //Verify that handled bit is used correctly _ASSERT(RP_HANDLED & RP_DELIVERED); _ASSERT(RP_HANDLED & RP_DSN_SENT_NDR); _ASSERT(RP_HANDLED & RP_FAILED); _ASSERT(RP_HANDLED & RP_UNRESOLVED); _ASSERT(RP_HANDLED & RP_EXPANDED); _ASSERT(RP_HANDLED ^ RP_DELIVERED); _ASSERT(RP_HANDLED ^ RP_DSN_SENT_NDR); _ASSERT(RP_HANDLED ^ RP_FAILED); _ASSERT(RP_HANDLED ^ RP_UNRESOLVED); _ASSERT(RP_HANDLED ^ RP_EXPANDED); //Verify that DSN-handled bit is used correctly _ASSERT(RP_DSN_HANDLED & RP_DSN_SENT_NDR); _ASSERT(RP_DSN_HANDLED & RP_DSN_SENT_EXPANDED); _ASSERT(RP_DSN_HANDLED & RP_DSN_SENT_RELAYED); _ASSERT(RP_DSN_HANDLED & RP_DSN_SENT_DELIVERED); _ASSERT(RP_DSN_HANDLED ^ RP_DSN_SENT_NDR); _ASSERT(RP_DSN_HANDLED ^ RP_DSN_SENT_EXPANDED); _ASSERT(RP_DSN_HANDLED ^ RP_DSN_SENT_RELAYED); _ASSERT(RP_DSN_HANDLED ^ RP_DSN_SENT_DELIVERED); //Verify that general failure bit is used correctly _ASSERT(RP_GENERAL_FAILURE & RP_FAILED); _ASSERT(RP_GENERAL_FAILURE & RP_UNRESOLVED); _ASSERT(RP_GENERAL_FAILURE ^ RP_FAILED); _ASSERT(RP_GENERAL_FAILURE ^ RP_UNRESOLVED); } //---[ VerifyMarkedRecips ]---------------------------------------------------- // // ***DEBUG ONLY*** // // Description: // Verifies that all recipients have been marked as appropriate // Parameters: // pIMailMsgRecipients Recipients object to check for // dwStartDomain Starting domain for context // dwDSNAction DSN actions requested // Returns: // - // History: // 7/2/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDefaultDSNSink::VerifyMarkedRecips(IMailMsgRecipients *pIMailMsgRecipients, DWORD dwStartDomain,DWORD dwDSNActions) { HRESULT hr = S_OK; DWORD dwCurrentRecipFlags= 0; DWORD iCurrentRecip = 0; RECIPIENT_FILTER_CONTEXT rpfctxt; BOOL fContextInit = FALSE; DWORD dwCurrentDSNAction = 0; hr = pIMailMsgRecipients->InitializeRecipientFilterContext(&rpfctxt, dwStartDomain, 0, 0); if (FAILED(hr)) goto Exit; fContextInit = TRUE; hr = pIMailMsgRecipients->GetNextRecipient(&rpfctxt, &iCurrentRecip); while (SUCCEEDED(hr)) { hr = pIMailMsgRecipients->GetDWORD(iCurrentRecip, IMMPID_RP_RECIPIENT_FLAGS, &dwCurrentRecipFlags); if (FAILED(hr)) { _ASSERT(0 && "GetDWORD for IMMPID_RP_RECIPIENT_FLAGS FAILED"); goto Exit; } if (!(dwCurrentRecipFlags & (RP_DSN_HANDLED | RP_DSN_NOTIFY_NEVER))) { if (fdwGetDSNAction(dwDSNActions, &dwCurrentRecipFlags, &dwCurrentDSNAction)) { _ASSERT(0 && "Recipient not marked correctly by DSN code"); } } hr = pIMailMsgRecipients->GetNextRecipient(&rpfctxt, &iCurrentRecip); } if (HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) == hr) hr = S_OK; Exit: if (fContextInit) { hr = pIMailMsgRecipients->TerminateRecipientFilterContext(&rpfctxt); _ASSERT(SUCCEEDED(hr) && "TerminateRecipientFilterContext FAILED"); } } //---[ AssertMimeBoundary ]---------------------------------------------------- // // ***DEBUG ONLY*** // Description: // Asserts that the given MIME boundary is NULL-terminated and has only // Valid characters // Parameters: // szMimeBoundary NULL-terminated MIME Boundary string // Returns: // - // History: // 7/6/98 - MikeSwa Created // //----------------------------------------------------------------------------- void AssertMimeBoundary(LPSTR szMimeBoundary) { CHAR *pcharCurrent = szMimeBoundary; DWORD cChars = 0; while ('\0' != *pcharCurrent) { cChars++; _ASSERT(cChars <= MIME_BOUNDARY_RFC2046_LIMIT); _ASSERT(fIsValidMIMEBoundaryChar(*pcharCurrent)); pcharCurrent++; } } #else //not DEBUG #define _ASSERT_RECIP_FLAGS() #define _VERIFY_MARKED_RECIPS(a, b, c) #define _ASSERT_MIME_BOUNDARY(szMimeBoundary) #endif //DEBUG //---[ fIsMailMsgDSN ]--------------------------------------------------------- // // // Description: // Determines if a mailmsg is a DSN. // Parameters: // IN pIMailMsgProperties // Returns: // TRUE if the orinal message is a DSN // FALSE if it is not a DSN // History: // 2/11/99 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL fIsMailMsgDSN(IMailMsgProperties *pIMailMsgProperties) { CHAR szSenderBuffer[sizeof(DSN_MAIL_FROM)]; DWORD cbSender = 0; HRESULT hr = S_OK; BOOL fIsDSN = FALSE; //unless proven otherwise... it is not a DSN _ASSERT(pIMailMsgProperties); szSenderBuffer[0] = '\0'; //Get the sender of the original message hr = pIMailMsgProperties->GetProperty(IMMPID_MP_SENDER_ADDRESS_SMTP, sizeof(szSenderBuffer), &cbSender, (BYTE *) szSenderBuffer); if (SUCCEEDED(hr) && ('\0' == szSenderBuffer[0] || !strcmp(DSN_MAIL_FROM, szSenderBuffer))) { //If the sender is a NULL string... or "<>"... then it is a DSN fIsDSN = TRUE; } return fIsDSN; } //---[ HrResetRecipientFilter ]------------------------------------------------ // // // Description: // Resets the recipient filter conext // Parameters: // IN pIMailMsgRecipients Msg to reset context on // IN prpfctxt Recip filter context // IN dwStartDomain StartDomain // IN dwRecipFilterMask Bit mask of recipient flags we care about // IN dwRecipFilterFlags Values of flags that we are looking for // IN OUT pfContextInit TRUE if the prpfctxt has been initialized // Returns: // S_OK on success // Error code from IMailMsgProperties on failure // History: // 2/11/99 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT HrResetRecipientFilter(IMailMsgRecipients *pIMailMsgRecipients, RECIPIENT_FILTER_CONTEXT *prpfctxt, DWORD dwStartDomain, DWORD dwRecipFilterMask, DWORD dwRecipFilterFlags, BOOL *pfContextInit) { _ASSERT(pIMailMsgRecipients); _ASSERT(prpfctxt); _ASSERT(pfContextInit); HRESULT hr = S_OK; if (*pfContextInit) { //recycle context *pfContextInit = FALSE; hr = pIMailMsgRecipients->TerminateRecipientFilterContext(prpfctxt); _ASSERT(SUCCEEDED(hr) && "TerminateRecipientFilterContext FAILED!!!!"); if (FAILED(hr)) goto Exit; } hr = pIMailMsgRecipients->InitializeRecipientFilterContext(prpfctxt, dwStartDomain, dwRecipFilterMask, dwRecipFilterFlags); _ASSERT(SUCCEEDED(hr) && "InitializeRecipientFilterContext FAILED after succeeding once!!!!"); if (FAILED(hr)) goto Exit; *pfContextInit = TRUE; //now context is valid again Exit: return hr; } //---[ CDefaultDSNSink::CDefaultDSNSink ]-------------------------------------- // // // Description: // CDefaultDSNSink constructor // Parameters: // - // Returns: // // History: // 6/30/98 - MikeSwa Created // //----------------------------------------------------------------------------- CDefaultDSNSink::CDefaultDSNSink() { FILETIME ftStartTime; _ASSERT_RECIP_FLAGS(); m_fInit = FALSE; m_dwSignature = DSN_SINK_SIG; m_cDSNsRequested = 0; //Init string for MIME headers GetSystemTimeAsFileTime(&ftStartTime); wsprintf(m_szPerInstanceMimeBoundary, "%08X%08X", ftStartTime.dwHighDateTime, ftStartTime.dwLowDateTime); } //---[ CDefaultDSNSink::HrInitialize ]----------------------------------------- // // // Description: // Performs initialization... // - Sets init flag // - Currently nothing else // Parameters: // - // Returns: // S_OK on SUCCESS // History: // 7/3/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrInitialize() { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrInitialize"); HRESULT hr = S_OK; m_fInit = TRUE; srand(GetTickCount()); TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::~CDefaultDSNSink ]------------------------------------- // // // Description: // // Parameters: // // Returns: // // History: // 6/30/98 - MikeSwa Created // //----------------------------------------------------------------------------- CDefaultDSNSink::~CDefaultDSNSink() { m_dwSignature = DSN_SINK_SIG_FREED; } //---[ CDefaultDSNSink::QueryInterface ]--------------------------------------- // // // Description: // Implements IUnknown::QueryInterface for CDefaultDSNSink // Parameters: // IN riid GUID of interface looking for // OUT ppvObj Returned object // Returns: // S_OK onsuccess // E_INVALIDARG if ppvObj is NULL // E_NOINTERFACE if interface is not supported // History: // 6/30/98 - MikeSwa Created // //----------------------------------------------------------------------------- STDMETHODIMP CDefaultDSNSink::QueryInterface(REFIID riid, LPVOID * ppvObj) { HRESULT hr = S_OK; if (!m_fInit) { hr = AQUEUE_E_NOT_INITIALIZED; goto Exit; } if (!ppvObj) { hr = E_INVALIDARG; goto Exit; } if (IID_IUnknown == riid) { *ppvObj = static_cast(this); } else if (IID_IDSNGenerationSink == riid) { *ppvObj = static_cast(this); } else { *ppvObj = NULL; hr = E_NOINTERFACE; goto Exit; } static_cast(*ppvObj)->AddRef(); Exit: return hr; } //---[ CDefaultDSNSink::GenerateDSN ]------------------------------------------ // // // Description: // Implements IDSNGenerationSink::GenerateDSN. Generates a DSN // IMailMsgProperties and // Parameters: // pISMTPServer Interface used to generate DSN // pIMailMsgProperties IMailMsg to generate DSN for // dwStartDomain Domain to start recip context // dwDSNActions DSN action to perform // dwRFC821Status Global RFC821 status DWORD // hrStatus Global HRESULT status // szDefaultDomain Default domain (used to create FROM address) // cbDefaultDomain string length of szDefaultDomain // szReportingMTA Name of MTA requesting DSN generation // cbReportingMTA string length of szReportingMTA // szReportingMTAType Type of MTA requestiong DSN (SMTP is "dns" // cbReportingMTAType string length of szReportingMTAType // PreferredLangId Language to generate DSN in // dwDSNOptions Options flags as defined in aqueue.idl // szCopyNDRTo SMTP Address to copy NDR to // cbCopyNDRTo string lengtt of szCopyNDRTo // ppIMailMsgPeropertiesDSN Generated DSN. // pdwDSNTypesGenerated Describes the type(s) of DSN's generated // pcRecipsDSNd # of recipients that were DSN'd for this message // pcIterationsLeft # of times remaining that this function needs // to be called to generate all requested DSNs. // First-time caller should initialize to zero // Returns: // S_OK on success // AQUEUE_E_NDR_OF_DSN if attempting to NDR a DSN // History: // 6/30/98 - MikeSwa Created // 12/14/98 - MikeSwa Modified (Added pcIterationsLeft) // 10/13/1999 - MikeSwa Modified (Added szDefaultDomain) // // //----------------------------------------------------------------------------- STDMETHODIMP CDefaultDSNSink::GenerateDSN( ISMTPServer *pISMTPServer, IMailMsgProperties *pIMailMsgProperties, DWORD dwStartDomain, DWORD dwDSNActions, DWORD dwRFC821Status, HRESULT hrStatus, LPSTR szDefaultDomain, DWORD cbDefaultDomain, LPSTR szReportingMTA, DWORD cbReportingMTA, LPSTR szReportingMTAType, DWORD cbReportingMTAType, LPSTR szDSNContext, DWORD cbDSNContext, DWORD dwPreferredLangId, DWORD dwDSNOptions, LPSTR szCopyNDRTo, DWORD cbCopyNDRTo, IMailMsgProperties **ppIMailMsgPropertiesDSN, DWORD *pdwDSNTypesGenerated, DWORD *pcRecipsDSNd, DWORD *pcIterationsLeft) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::GenerateDSN"); HRESULT hr = S_OK; HRESULT hrTmp = S_OK; BOOL fContextInit = FALSE; BOOL fDSNNeeded = FALSE; DWORD iCurrentRecip = 0; DWORD dwCurrentRecipFlags = 0x00000000; DWORD dwRecipFilterMask = 0x00000000; DWORD dwRecipFilterFlags = 0x00000000; DWORD dwCurrentDSNAction = 0; DWORD dwDSNActionsNeeded = 0; //the type of DSNs that will actually be sent IMailMsgRecipients *pIMailMsgRecipients = NULL; IMailMsgProperties *pIMailMsgPropertiesDSN = NULL; ISMTPServerInternal *pISMTPServerInternal = NULL; PFIO_CONTEXT pDSNBody = NULL; RECIPIENT_FILTER_CONTEXT rpfctxt; CDSNBuffer dsnbuff; CHAR szMimeBoundary[MIME_BOUNDARY_SIZE]; DWORD cbMimeBoundary = 0; FILETIME ftExpireTime; DWORD cbCurrentSize = 0; //used to get size of returned property CHAR szExpireTimeBuffer[MAX_RFC822_DATE_SIZE]; LPSTR szExpireTime = NULL; //will point to szExpireTimeBuffer if found DWORD cbExpireTime = 0; _ASSERT(ppIMailMsgPropertiesDSN); _ASSERT(pISMTPServer); _ASSERT(pIMailMsgProperties); _ASSERT(pdwDSNTypesGenerated); _ASSERT(pcRecipsDSNd); _ASSERT(pcIterationsLeft); *pcRecipsDSNd = 0; *ppIMailMsgPropertiesDSN = NULL; *pdwDSNTypesGenerated = 0; GetCurrentMimeBoundary(szReportingMTA, cbReportingMTA, szMimeBoundary, &cbMimeBoundary); //Get Recipients interface hr = pIMailMsgProperties->QueryInterface(IID_IMailMsgRecipients, (PVOID *) &pIMailMsgRecipients); if (FAILED(hr)) goto Exit; //Use recipient context to loop over recipients hr = HrGetFilterMaskAndFlags(dwDSNActions, &dwRecipFilterFlags, &dwRecipFilterMask); if (FAILED(hr)) goto Exit; hr = HrResetRecipientFilter(pIMailMsgRecipients, &rpfctxt, dwStartDomain, dwRecipFilterMask, dwRecipFilterFlags, &fContextInit); if (FAILED(hr)) goto Exit; //Loop over recipients to make sure we can need to allocate a message hr = pIMailMsgRecipients->GetNextRecipient(&rpfctxt, &iCurrentRecip); while (SUCCEEDED(hr)) { hr = pIMailMsgRecipients->GetDWORD(iCurrentRecip, IMMPID_RP_RECIPIENT_FLAGS, &dwCurrentRecipFlags); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Failure 0x%08X to get flags for recip %d", hr, iCurrentRecip); goto Exit; } DebugTrace((LPARAM) this, "Recipient %d with flags 0x%08X found", iCurrentRecip, dwCurrentRecipFlags); if (fdwGetDSNAction(dwDSNActions, &dwCurrentRecipFlags, &dwCurrentDSNAction)) fDSNNeeded = TRUE; //keep track of the types of DSN's we will be generating dwDSNActionsNeeded |= dwCurrentDSNAction; hr = pIMailMsgRecipients->GetNextRecipient(&rpfctxt, &iCurrentRecip); } if (HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) == hr) hr = S_OK; //we just reached the end of the context else if (FAILED(hr)) ErrorTrace((LPARAM) this, "GetNextRecipient failed with 0x%08X",hr); if (!fDSNNeeded) { DebugTrace((LPARAM) this, "Do not need to generate a 0x%08X DSN", dwDSNActions, pIMailMsgProperties); *pcIterationsLeft = 0; goto Exit; //don't create a message object if we don't have to } //Check if message is a DSN (we will not genrate a DSN of a DSN) //This must be checked after we run through the recipients, because //we need to check them to keep from badmailing this message //multiple times. if (fIsMailMsgDSN(pIMailMsgProperties)) { DebugTrace((LPARAM) pIMailMsgProperties, "Message is a DSN"); *pcIterationsLeft = 0; if (dwDSNActions & (DSN_ACTION_FAILURE | DSN_ACTION_FAILURE_ALL)) { //NDR of DSN... return special error code hr = AQUEUE_E_NDR_OF_DSN; //mark all the appropriate recipient flags so we don't //generate 2 badmails HrMarkAllRecipFlags(dwDSNActions, pIMailMsgRecipients, &rpfctxt); } goto Exit; } //if we can generate a failure DSN and the orginal request was for //fail *all* make sure dwDSNActionNeeded reflects this if ((DSN_ACTION_FAILURE & dwDSNActionsNeeded) && (DSN_ACTION_FAILURE_ALL & dwDSNActions)) dwDSNActionsNeeded |= DSN_ACTION_FAILURE_ALL; GetCurrentIterationsDSNAction(&dwDSNActionsNeeded, pcIterationsLeft); if (!dwDSNActionsNeeded) { fDSNNeeded = FALSE; *pcIterationsLeft = 0; goto Exit; //don't create a message object if we don't have to } //recycle context hr = HrResetRecipientFilter(pIMailMsgRecipients, &rpfctxt, dwStartDomain, dwRecipFilterMask, dwRecipFilterFlags, &fContextInit); if (FAILED(hr)) goto Exit; hr = pISMTPServer->QueryInterface(IID_ISMTPServerInternal, (void **) &pISMTPServerInternal); if (FAILED(hr)) goto Exit; hr = pISMTPServerInternal->AllocBoundMessage(&pIMailMsgPropertiesDSN, &pDSNBody); if (FAILED(hr)) goto Exit; //workaround to handle AllocBoundMessage on shutdown if (NULL == pDSNBody) { hr = HRESULT_FROM_WIN32(ERROR_NO_SYSTEM_RESOURCES); ErrorTrace((LPARAM) this, "ERROR: AllocBoundMessage failed silently"); goto Exit; } //Associate file handle with CDSNBuffer hr = dsnbuff.HrInitialize(pDSNBody); if (FAILED(hr)) goto Exit; //Get MsgExpire Time //Write DSN_RP_HEADER_RETRY_UNTIL using expire FILETIME hr = pIMailMsgProperties->GetProperty(IMMPID_MP_EXPIRE_NDR, sizeof(FILETIME), &cbCurrentSize, (BYTE *) &ftExpireTime); if (SUCCEEDED(hr)) { _ASSERT(sizeof(FILETIME) == cbCurrentSize); //convert to internet standard szExpireTime = szExpireTimeBuffer; FileTimeToLocalRFC822Date(ftExpireTime, szExpireTime); cbExpireTime = lstrlen(szExpireTime); } else if (MAILMSG_E_PROPNOTFOUND == hr) hr = S_OK; else goto Exit; hr = HrWriteDSNP1AndP2Headers(dwDSNActionsNeeded, pIMailMsgProperties, pIMailMsgPropertiesDSN, &dsnbuff, szDefaultDomain, cbDefaultDomain, szReportingMTA, cbReportingMTA, szDSNContext, cbDSNContext, szCopyNDRTo, hrStatus, szMimeBoundary, cbMimeBoundary, dwDSNOptions); if (FAILED(hr)) goto Exit; hr = HrWriteDSNHumanReadable(pIMailMsgPropertiesDSN, pIMailMsgRecipients, &rpfctxt, dwDSNActionsNeeded, &dsnbuff, dwPreferredLangId, szMimeBoundary, cbMimeBoundary, hrStatus); if (FAILED(hr)) goto Exit; hr = HrWriteDSNReportPerMsgProperties(pIMailMsgProperties, &dsnbuff, szReportingMTA, cbReportingMTA, szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; //recycle context again (may be used during generation of human readable) hr = HrResetRecipientFilter(pIMailMsgRecipients, &rpfctxt, dwStartDomain, dwRecipFilterMask, dwRecipFilterFlags, &fContextInit); if (FAILED(hr)) goto Exit; //$$REVIEW - Do we need to keep an "undo" list... or perhaps reverse //engineer what the previous value was in case of a failure hr = pIMailMsgRecipients->GetNextRecipient(&rpfctxt, &iCurrentRecip); while (SUCCEEDED(hr)) { hr = pIMailMsgRecipients->GetDWORD(iCurrentRecip, IMMPID_RP_RECIPIENT_FLAGS, &dwCurrentRecipFlags); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Failure 0x%08X to get flags for recip %d (per-recip pass)", hr, iCurrentRecip); goto Exit; } DebugTrace((LPARAM) this, "Recipient %d with flags 0x%08X found (per-recip pass)", iCurrentRecip, dwCurrentRecipFlags); if (fdwGetDSNAction(dwDSNActionsNeeded, &dwCurrentRecipFlags, &dwCurrentDSNAction)) { *pdwDSNTypesGenerated |= (dwCurrentDSNAction & DSN_ACTION_TYPE_MASK); (*pcRecipsDSNd)++; hr = HrWriteDSNReportPreRecipientProperties(pIMailMsgRecipients, &dsnbuff, dwCurrentRecipFlags, iCurrentRecip, szExpireTime, cbExpireTime, dwCurrentDSNAction, dwRFC821Status, hrStatus); if (FAILED(hr)) goto Exit; hr = pIMailMsgRecipients->PutDWORD(iCurrentRecip, IMMPID_RP_RECIPIENT_FLAGS, dwCurrentRecipFlags); _ASSERT(SUCCEEDED(hr) && "PutDWORD for IMMPID_RP_RECIPIENT_FLAGS FAILED on 2nd pass"); if (FAILED(hr)) goto Exit; } hr = pIMailMsgRecipients->GetNextRecipient(&rpfctxt, &iCurrentRecip); } if (HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) == hr) hr = S_OK; if (!(*pcRecipsDSNd)) goto Exit; //no work to do _VERIFY_MARKED_RECIPS(pIMailMsgRecipients, dwStartDomain, dwDSNActionsNeeded); hr = HrWriteDSNClosingAndOriginalMessage(pIMailMsgProperties, pIMailMsgPropertiesDSN, &dsnbuff, pDSNBody, dwDSNActionsNeeded, szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; hr = pIMailMsgPropertiesDSN->Commit(NULL); if (FAILED(hr)) { ErrorTrace((LPARAM) pIMailMsgProperties, "ERROR: IMailMsg::Commit failed - hr 0x%08X", hr); goto Exit; } *ppIMailMsgPropertiesDSN = pIMailMsgPropertiesDSN; pIMailMsgPropertiesDSN = NULL; Exit: if (pIMailMsgRecipients) { if (fContextInit) { hrTmp = pIMailMsgRecipients->TerminateRecipientFilterContext(&rpfctxt); if (FAILED(hrTmp)) { _ASSERT(0 && "TerminateRecipientFilterContext Failed"); ErrorTrace((LPARAM) this, "ERROR: TerminateRecipientFilterContext failed - hr 0x%08X", hrTmp); if (SUCCEEDED(hr)) hr = hrTmp; } } pIMailMsgRecipients->Release(); } if (pIMailMsgPropertiesDSN) { IMailMsgQueueMgmt *pIMailMsgQueueMgmt = NULL; //if non-NULL, then we should not be returning any value _ASSERT(NULL == *ppIMailMsgPropertiesDSN); //Check for alloc bound message failure if (HRESULT_FROM_WIN32(ERROR_NO_SYSTEM_RESOURCES) != hr) { if (SUCCEEDED(pIMailMsgPropertiesDSN->QueryInterface(IID_IMailMsgQueueMgmt, (void **) &pIMailMsgQueueMgmt))) { _ASSERT(pIMailMsgQueueMgmt); pIMailMsgQueueMgmt->Delete(NULL); pIMailMsgQueueMgmt->Release(); } } pIMailMsgPropertiesDSN->Release(); } if (pISMTPServerInternal) pISMTPServerInternal->Release(); if (FAILED(hr)) *pcIterationsLeft = 0; //workaround for alloc bound message if (HRESULT_FROM_WIN32(ERROR_NO_SYSTEM_RESOURCES) == hr) { hr = S_OK; } TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::GetFilterMaskAndFlags ]-------------------------------- // // // Description: // Determines what the appropriate mask and flags for a recip serch filter // are based on the given actions. // // It may not be possible to constuct a perfectly optimal search (ie Failed // and delivered).... this function will attempt to find the "most optimal" // search possible. // Parameters: // dwDSNActions Requested DSN generation operations // pdwRecipMask Mask to pass to InitializeRecipientFilterContext // pdwRecipFlags Flags to pass to InitializeRecipientFilterContext // Returns: // S_OK on success // E_INVALIDARG if invalid combinations are given // History: // 7/1/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrGetFilterMaskAndFlags(IN DWORD dwDSNActions, OUT DWORD *pdwRecipMask, OUT DWORD *pdwRecipFlags) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrGetFilterMaskAndFlags"); HRESULT hr = S_OK; _ASSERT(pdwRecipMask); _ASSERT(pdwRecipFlags); //in general we are only interested in un-DSN'd recipients *pdwRecipFlags = 0x00000000; *pdwRecipMask = RP_DSN_HANDLED | RP_DSN_NOTIFY_NEVER; //Note these searches are just optimizations... so we don't look at //recipients we don't need to. However, it may not be possible to //limit the search precisely if (DSN_ACTION_FAILURE == dwDSNActions) { //We are interested in hard failures *pdwRecipMask |= RP_GENERAL_FAILURE; *pdwRecipFlags |= RP_GENERAL_FAILURE; } if (!((DSN_ACTION_DELIVERED | DSN_ACTION_RELAYED) & dwDSNActions)) { //are not interested in delivered if ((DSN_ACTION_FAILURE_ALL | DSN_ACTION_DELAYED) & dwDSNActions) { //it is safe to check only undelivered *pdwRecipMask |= (RP_DELIVERED ^ RP_HANDLED); //must be un-set _ASSERT(!(*pdwRecipFlags & (RP_DELIVERED ^ RP_HANDLED))); } } else { //$$TODO - can narrow this search more //we are interested in delivered if (!((DSN_ACTION_FAILURE_ALL | DSN_ACTION_FAILURE| DSN_ACTION_DELAYED) & dwDSNActions)) { //it is safe to check only delivered *pdwRecipMask |= RP_DELIVERED; *pdwRecipFlags |= RP_DELIVERED; } } DebugTrace((LPARAM) this, "DSN Action 0x%08X, Recip mask 0x%08X, Recip flags 0x%08X", dwDSNActions, pdwRecipMask, pdwRecipFlags); TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::fdwGetDSNAction ]-------------------------------------- // // // Description: // Determines what DSN action needs to happen on a recipient based on // the requested DSN actions and the recipient flags // Parameters: // IN dwDSNAction The requested DSN actions // IN OUT pdwCurrentRecipFlags The flags for current recipient... set to // what there new value should be // OUT pdwCurrentDSNAction The DSN action that needs to be performed // On this recipient (DSN_ACTION_FAILURE is // used to denote sending a NDR) // Returns: // TRUE if some DSN action must be taken for this recipient // FALSE if no DSN action is need // History: // 7/2/98 - MikeSwa Created // //----------------------------------------------------------------------------- BOOL CDefaultDSNSink::fdwGetDSNAction(IN DWORD dwDSNAction, IN OUT DWORD *pdwCurrentRecipFlags, OUT DWORD *pdwCurrentDSNAction) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::fdwGetDSNAction"); _ASSERT(pdwCurrentRecipFlags); BOOL fResult = FALSE; DWORD dwOriginalRecipFlags = *pdwCurrentRecipFlags; DWORD dwFlags = 0; //This should never be hit because of the filter _ASSERT(!(*pdwCurrentRecipFlags & (RP_DSN_HANDLED | RP_DSN_NOTIFY_NEVER))); if (DSN_ACTION_FAILURE & dwDSNAction) { if ((RP_GENERAL_FAILURE & *pdwCurrentRecipFlags) && ((RP_DSN_NOTIFY_FAILURE & *pdwCurrentRecipFlags) || (!(RP_DSN_NOTIFY_MASK & *pdwCurrentRecipFlags)))) { DebugTrace((LPARAM) this, "Recipient matched for FAILURE DSN"); fResult = TRUE; *pdwCurrentRecipFlags |= RP_DSN_SENT_NDR; *pdwCurrentDSNAction = DSN_ACTION_FAILURE; goto Exit; } } if (DSN_ACTION_FAILURE_ALL & dwDSNAction) { //Fail all non-delivered that we haven't sent notifications for if (((!((RP_DSN_HANDLED | (RP_DELIVERED ^ RP_HANDLED)) & *pdwCurrentRecipFlags))) && ((RP_DSN_NOTIFY_FAILURE & *pdwCurrentRecipFlags) || (!(RP_DSN_NOTIFY_MASK & *pdwCurrentRecipFlags)))) { //Don't send failures for expanded DL;s if (RP_EXPANDED != (*pdwCurrentRecipFlags & RP_EXPANDED)) { DebugTrace((LPARAM) this, "Recipient matched for FAILURE (all) DSN"); fResult = TRUE; *pdwCurrentRecipFlags |= RP_DSN_SENT_NDR; *pdwCurrentDSNAction = DSN_ACTION_FAILURE; goto Exit; } } } if (DSN_ACTION_DELAYED & dwDSNAction) { //send at most 1 delay DSN //Also send only if DELAY was requested or no specific instructions were //specified if ((!((RP_DSN_SENT_DELAYED | RP_HANDLED) & *pdwCurrentRecipFlags)) && ((RP_DSN_NOTIFY_DELAY & *pdwCurrentRecipFlags) || (!(RP_DSN_NOTIFY_MASK & *pdwCurrentRecipFlags)))) { DebugTrace((LPARAM) this, "Recipient matched for DELAYED DSN"); fResult = TRUE; *pdwCurrentRecipFlags |= RP_DSN_SENT_DELAYED; *pdwCurrentDSNAction = DSN_ACTION_DELAYED; goto Exit; } } if (DSN_ACTION_RELAYED & dwDSNAction) { //send relay if it was delivered *and* DSN not supported by remote MTA //*and* notification of success was explicitly requested dwFlags = (RP_DELIVERED ^ RP_HANDLED) | RP_REMOTE_MTA_NO_DSN | RP_DSN_NOTIFY_SUCCESS; if ((dwFlags & *pdwCurrentRecipFlags) == dwFlags) { DebugTrace((LPARAM) this, "Recipient matched for RELAYED DSN"); fResult = TRUE; *pdwCurrentRecipFlags |= RP_DSN_SENT_RELAYED; *pdwCurrentDSNAction = DSN_ACTION_RELAYED; goto Exit; } } if (DSN_ACTION_DELIVERED & dwDSNAction) { //send delivered if it was delivered *and* no DSN sent yet dwFlags = (RP_DELIVERED ^ RP_HANDLED) | RP_DSN_NOTIFY_SUCCESS; _ASSERT(!(*pdwCurrentRecipFlags & RP_DSN_HANDLED)); //should be filtered out if ((dwFlags & *pdwCurrentRecipFlags) == dwFlags) { DebugTrace((LPARAM) this, "Recipient matched for SUCCESS DSN"); fResult = TRUE; *pdwCurrentRecipFlags |= RP_DSN_SENT_DELIVERED; *pdwCurrentDSNAction = DSN_ACTION_DELIVERED; goto Exit; } } if (DSN_ACTION_EXPANDED & dwDSNAction) { //Send expanded if the recipient is marked as expanded and //NOTIFY=SUCCESS was requested if ((RP_EXPANDED == (*pdwCurrentRecipFlags & RP_EXPANDED)) && (*pdwCurrentRecipFlags & RP_DSN_NOTIFY_SUCCESS) && !(*pdwCurrentRecipFlags & RP_DSN_SENT_EXPANDED)) { DebugTrace((LPARAM) this, "Recipient matched for EXPANDED DSN"); fResult = TRUE; *pdwCurrentRecipFlags |= RP_DSN_SENT_EXPANDED; *pdwCurrentDSNAction = DSN_ACTION_EXPANDED; goto Exit; } } Exit: if (!fResult) { DebugTrace((LPARAM) this, "Recip with flags 0x%08X will not generate DSN for action 0x%08X", dwOriginalRecipFlags, dwDSNAction); } TraceFunctLeave(); return fResult; } //---[ CDefaultDSNSink::GetCurrentMimeBoundary ]------------------------------- // // // Description: // Creates unique MIME-boundary for message. // // Format we are using for boundary is string versions of the following: // MIME_BOUNDARY_CONSTANT // FILETIME at start // DWORD count of DSNs Requested // 16 bytes of our virtual server's domain name // Parameters: // IN szReportingMTA reporting MTA // IN cbReportingMTA String length of reporting MTA // IN OUT szMimeBoundary Buffer to put boundary in (size is MIME_BOUNDARY_SIZE) // OUT cbMimeBoundary Amount of buffer used for MIME Boundary // Returns: // - // History: // 7/6/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDefaultDSNSink::GetCurrentMimeBoundary( IN LPSTR szReportingMTA, IN DWORD cbReportingMTA, IN OUT CHAR szMimeBoundary[MIME_BOUNDARY_SIZE], OUT DWORD *pcbMimeBoundary) { _ASSERT(MIME_BOUNDARY_RFC2046_LIMIT >= MIME_BOUNDARY_SIZE); DWORD iCurrentOffset = 0; szMimeBoundary[MIME_BOUNDARY_SIZE-1] = '\0'; CHAR *pcharCurrent = NULL; CHAR *pcharStop = NULL; memcpy(szMimeBoundary+iCurrentOffset, MIME_BOUNDARY_CONSTANT, sizeof(MIME_BOUNDARY_CONSTANT)-1); iCurrentOffset += sizeof(MIME_BOUNDARY_CONSTANT)-1; memcpy(szMimeBoundary+iCurrentOffset, m_szPerInstanceMimeBoundary, MIME_BOUNDARY_START_TIME_SIZE); iCurrentOffset += MIME_BOUNDARY_START_TIME_SIZE; wsprintf(szMimeBoundary+iCurrentOffset, "%08X", InterlockedIncrement((PLONG) &m_cDSNsRequested)); iCurrentOffset += 8; if (cbReportingMTA >= MIME_BOUNDARY_SIZE-iCurrentOffset) { memcpy(szMimeBoundary+iCurrentOffset, szReportingMTA, MIME_BOUNDARY_SIZE-iCurrentOffset - 1); *pcbMimeBoundary = MIME_BOUNDARY_SIZE-1; } else { memcpy(szMimeBoundary+iCurrentOffset, szReportingMTA, cbReportingMTA); szMimeBoundary[iCurrentOffset + cbReportingMTA] = '\0'; *pcbMimeBoundary = iCurrentOffset + cbReportingMTA; } //Now we need to verify that the passed in string can be part of a valid //MIME Header pcharStop = szMimeBoundary + *pcbMimeBoundary; for (pcharCurrent = szMimeBoundary + iCurrentOffset; pcharCurrent < pcharStop; pcharCurrent++) { if (!fIsValidMIMEBoundaryChar(*pcharCurrent)) *pcharCurrent = '?'; //turn it into a valid character } _ASSERT_MIME_BOUNDARY(szMimeBoundary); _ASSERT('\0' == szMimeBoundary[MIME_BOUNDARY_SIZE-1]); } //---[ CDefaultDSNSink::HrWriteDSNP1AndP2Headers ]----------------------------- // // // Description: // Writes global DSN P1 Properties to IMailMsgProperties // Parameters: // dwDSNAction DSN action specified for sink // pIMailMsgProperties Msg that DSN is being generated for // pIMailMsgPropertiesDSN DSN being generated // psndbuff Buffer to write to // szDefaultDomain Default domain - used from postmaster from address // cbDefaultDomain strlen of szDefaultDomain // szReportingMTA Reporting MTA as passed to event sink // cbReportingMTA strlen of szReportingMTA // szDSNConext Debug File and line number info passed in // cbDSNConext strlen of szDSNContext // szCopyNDRTo SMTP Address to copy NDR to // hrStatus Status to record in DSN context // szMimeBoundary MIME boundary string // cbMimeBoundary strlen of MIME boundary // dwDSNOptions DSN Options flags // Returns: // S_OK on success // History: // 7/5/98 - MikeSwa Created // 8/14/98 - MikeSwa Modified - Added DSN context headers // 11/9/98 - MikeSwa Added copy NDR to functionality // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteDSNP1AndP2Headers( IN DWORD dwDSNAction, IN IMailMsgProperties *pIMailMsgProperties, IN IMailMsgProperties *pIMailMsgPropertiesDSN, IN CDSNBuffer *pdsnbuff, IN LPSTR szDefaultDomain, IN DWORD cbDefaultDomain, IN LPSTR szReportingMTA, IN DWORD cbReportingMTA, IN LPSTR szDSNContext, IN DWORD cbDSNContext, IN LPSTR szCopyNDRTo, IN HRESULT hrStatus, IN LPSTR szMimeBoundary, IN DWORD cbMimeBoundary, IN DWORD dwDSNOptions) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrWriteDSNP1AndP2Headers"); HRESULT hr = S_OK; HRESULT hrTmp = S_OK; CHAR szBuffer[512]; LPSTR szSender = (LPSTR) szBuffer; //tricks to avoid AV'ing in AddPrimary IMailMsgRecipientsAdd *pIMailMsgRecipientsAdd = NULL; IMailMsgRecipients *pIMailMsgRecipients = NULL; DWORD dwRecipAddressProp = IMMPID_RP_ADDRESS_SMTP; DWORD dwSMTPAddressProp = IMMPID_RP_ADDRESS_SMTP; DWORD iCurrentAddressProp = 0; DWORD dwDSNRecipient = 0; DWORD cbPostMaster = 0; CHAR szDSNAction[15]; FILETIME ftCurrentTime; CHAR szCurrentTimeBuffer[MAX_RFC822_DATE_SIZE]; _ASSERT(pIMailMsgProperties); _ASSERT(pIMailMsgPropertiesDSN); _ASSERT(pdsnbuff); szBuffer[0] = '\0'; //Get and write Message tracking properties hr = pIMailMsgProperties->GetStringA(IMMPID_MP_SERVER_VERSION, sizeof(szBuffer), szBuffer); if (SUCCEEDED(hr)) { hr = pIMailMsgPropertiesDSN->PutStringA(IMMPID_MP_SERVER_VERSION, szBuffer); if (FAILED(hr)) DebugTrace((LPARAM) this, "Warning: Unable to write version to msg - 0x%08X", hr); hr = S_OK; } else { DebugTrace((LPARAM) this, "Warning: Unable to get server version from msg - 0x%08X", hr); hr = S_OK; //ignore this non-fatal error } hr = pIMailMsgProperties->GetStringA(IMMPID_MP_SERVER_NAME, sizeof(szBuffer), szBuffer); if (SUCCEEDED(hr)) { hr = pIMailMsgPropertiesDSN->PutStringA(IMMPID_MP_SERVER_NAME, szBuffer); if (FAILED(hr)) DebugTrace((LPARAM) this, "Warning: Unable to write server name to msg - 0x%08X", hr); hr = S_OK; } else { DebugTrace((LPARAM) this, "Warning: Unable to get server name from msg - 0x%08X", hr); hr = S_OK; //ignore this non-fatal error } //Set the type of message if (dwDSNAction & (DSN_ACTION_EXPANDED | DSN_ACTION_RELAYED | DSN_ACTION_DELIVERED)) { hr = pIMailMsgPropertiesDSN->PutDWORD(IMMPID_MP_MSGCLASS, MP_MSGCLASS_DELIVERY_REPORT); } else if (dwDSNAction & (DSN_ACTION_FAILURE | DSN_ACTION_FAILURE_ALL | DSN_ACTION_DELAYED)) { hr = pIMailMsgPropertiesDSN->PutDWORD(IMMPID_MP_MSGCLASS, MP_MSGCLASS_NONDELIVERY_REPORT); } if (FAILED(hr)) { DebugTrace((LPARAM) this, "Warning: Unable to set msg class for dsn - 0x%08X", hr); hr = S_OK; } for (iCurrentAddressProp = 0; iCurrentAddressProp < NUM_DSN_ADDRESS_PROPERTIES; iCurrentAddressProp++) { szBuffer[0] = '\0'; //Get the sender of the original message hr = pIMailMsgProperties->GetStringA( g_rgdwSenderPropIDs[iCurrentAddressProp], sizeof(szBuffer), szBuffer); if (FAILED(hr) && (MAILMSG_E_PROPNOTFOUND != hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to get 0x%X sender of IMailMsg %p", g_rgdwSenderPropIDs[iCurrentAddressProp], pIMailMsgProperties); goto Exit; } // // If we have found an address break // if (SUCCEEDED(hr)) break; } // // If we failed to get a property... bail // if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to get any sender of IMailMsg 0x%08X", pIMailMsgProperties); goto Exit; } //write DSN Sender (P1) hr = pIMailMsgPropertiesDSN->PutProperty(IMMPID_MP_SENDER_ADDRESS_SMTP, sizeof(DSN_MAIL_FROM), (BYTE *) DSN_MAIL_FROM); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to write P1 DSN sender for IMailMsg 0x%08X", pIMailMsgProperties); goto Exit; } //write DSN Recipient hr = pIMailMsgPropertiesDSN->QueryInterface(IID_IMailMsgRecipients, (void **) &pIMailMsgRecipients); _ASSERT(SUCCEEDED(hr) && "QueryInterface for IID_IMailMsgRecipients failed"); if (FAILED(hr)) goto Exit; hr = pIMailMsgRecipients->AllocNewList(&pIMailMsgRecipientsAdd); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to Alloc List for DSN generation of IMailMsg 0x%08X", pIMailMsgProperties); goto Exit; } dwRecipAddressProp = g_rgdwRecipPropIDs[iCurrentAddressProp]; hr = pIMailMsgRecipientsAdd->AddPrimary( 1, (LPCSTR *) &szSender, &dwRecipAddressProp, &dwDSNRecipient, NULL, 0); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to write DSN recipient for IMailMsg 0x%p hr - 0x%08X", pIMailMsgProperties, hr); goto Exit; } //Write Address to copy NDR to (NDRs only) if (szCopyNDRTo && (dwDSNAction & (DSN_ACTION_FAILURE | DSN_ACTION_FAILURE_ALL))) { hr = pIMailMsgRecipientsAdd->AddPrimary( 1, (LPCSTR *) &szCopyNDRTo, &dwSMTPAddressProp, &dwDSNRecipient, NULL, 0); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "ERROR: Unable to write DSN recipient for IMailMsg 0x%08X", pIMailMsgProperties); goto Exit; } } //write P2 DSN sender hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RFC822_SENDER, sizeof(DSN_RFC822_SENDER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szDefaultDomain, cbDefaultDomain); if (FAILED(hr)) goto Exit; // // If we do not have a SMTP address, write a blank BCC instead of // a TO address (since we do not have a address we can write in the 822. // This is similar to what we do with the pickup dir when we have no TO // headers. // if (IMMPID_MP_SENDER_ADDRESS_SMTP == g_rgdwSenderPropIDs[iCurrentAddressProp]) { //Write P2 "To:" header (using the szSender value we determined above) hr = pdsnbuff->HrWriteBuffer((BYTE *) TO_HEADER, sizeof(TO_HEADER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szSender, lstrlen(szSender)); if (FAILED(hr)) goto Exit; //Save recipient (original sender) for Queue Admin/Message Tracking hr = pIMailMsgPropertiesDSN->PutStringA(IMMPID_MP_RFC822_TO_ADDRESS, szSender); if (FAILED(hr)) goto Exit; } else { hr = pdsnbuff->HrWriteBuffer((BYTE *) BCC_HEADER, sizeof(BCC_HEADER)-1); if (FAILED(hr)) goto Exit; } //Use szBuffer to construct 822 from to set for Queue Admin/Msg Tracking //"Postmaster@" + max of 64 characters should be less than 1/2 K!! _ASSERT(sizeof(szBuffer) > sizeof(DSN_SENDER_ADDRESS_PREFIX) + cbReportingMTA); memcpy(szBuffer, DSN_SENDER_ADDRESS_PREFIX, sizeof(DSN_SENDER_ADDRESS_PREFIX)); strncat(szBuffer, szDefaultDomain, sizeof(szBuffer) - sizeof(DSN_SENDER_ADDRESS_PREFIX)); hr = pIMailMsgPropertiesDSN->PutStringA(IMMPID_MP_RFC822_FROM_ADDRESS, szSender); if (FAILED(hr)) goto Exit; //Write P2 "Date:" header hr = pdsnbuff->HrWriteBuffer((BYTE *) DATE_HEADER, sizeof(DATE_HEADER)-1); if (FAILED(hr)) goto Exit; //Get current time GetSystemTimeAsFileTime(&ftCurrentTime); FileTimeToLocalRFC822Date(ftCurrentTime, szCurrentTimeBuffer); hr = pdsnbuff->HrWriteBuffer((BYTE *) szCurrentTimeBuffer, lstrlen(szCurrentTimeBuffer)); if (FAILED(hr)) goto Exit; //Write the MIME header hr = pdsnbuff->HrWriteBuffer( (BYTE *) MIME_HEADER, sizeof(MIME_HEADER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) "\"", sizeof(CHAR)); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) "\"", sizeof(CHAR)); if (FAILED(hr)) goto Exit; //write x-DSNContext header if (DSN_OPTIONS_WRITE_CONTEXT & dwDSNOptions) { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CONTEXT_HEADER, sizeof(DSN_CONTEXT_HEADER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szDSNContext, cbDSNContext); if (FAILED(hr)) goto Exit; wsprintf(szDSNAction, " (0x%08X)", dwDSNAction); hr = pdsnbuff->HrWriteBuffer((BYTE *) szDSNAction, 13); if (FAILED(hr)) goto Exit; wsprintf(szDSNAction, " (0x%08X)", hrStatus); hr = pdsnbuff->HrWriteBuffer((BYTE *) szDSNAction, 13); if (FAILED(hr)) goto Exit; } //Get and write the message ID if (fGenerateDSNMsgID(szReportingMTA, cbReportingMTA, szBuffer, sizeof(szBuffer))) { hr = pdsnbuff->HrWriteBuffer((BYTE *) MSGID_HEADER, sizeof(MSGID_HEADER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szBuffer, strlen(szBuffer)); if (FAILED(hr)) goto Exit; hr = pIMailMsgPropertiesDSN->PutStringA(IMMPID_MP_RFC822_MSG_ID, szBuffer); if (FAILED(hr)) goto Exit; } Exit: if (pIMailMsgRecipients) { if (pIMailMsgRecipientsAdd) { hrTmp = pIMailMsgRecipients->WriteList( pIMailMsgRecipientsAdd ); _ASSERT(SUCCEEDED(hrTmp) && "Go Get Keith"); if (FAILED(hrTmp) && SUCCEEDED(hr)) hr = hrTmp; } pIMailMsgRecipients->Release(); } if (pIMailMsgRecipientsAdd) pIMailMsgRecipientsAdd->Release(); TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrWriteDSNHumanReadable ]------------------------------ // // // Description: // Write human readable portion of DSN (including subject header) // Parameters: // pIMailMsgProperties Message DSN is being generated for // pIMailMsgREcipeints Recip Interface for Message // prpfctxt Delivery context that DSN's are being generated for // dwDSNActions DSN actions being taken (after looking at recips) // So we can generate a reasonable subject // pdsnbuff DSN Buffer to write content to // PreferredLangId Preferred language to generate DSN in // szMimeBoundary MIME boundary string // cbMimeBoundary strlen of MIME boundary // hrStatus Status to use to decide which text to display // Returns: // S_OK on success // History: // 7/5/98 - MikeSwa Created // 12/15/98 - MikeSwa Added list of recipients & fancy human readable // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteDSNHumanReadable( IN IMailMsgProperties *pIMailMsgPropertiesDSN, IN IMailMsgRecipients *pIMailMsgRecipients, IN RECIPIENT_FILTER_CONTEXT *prpfctxt, IN DWORD dwDSNActions, IN CDSNBuffer *pdsnbuff, IN DWORD dwPreferredLangId, IN LPSTR szMimeBoundary, IN DWORD cbMimeBoundary, IN HRESULT hrStatus) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrWriteDSNHumanReadable"); HRESULT hr = S_OK; DWORD dwDSNType = (dwDSNActions & DSN_ACTION_TYPE_MASK); LANGID LangID = (LANGID) dwPreferredLangId; CUTF7ConversionContext utf7conv(FALSE); CUTF7ConversionContext utf7convSubject(TRUE); BOOL fWriteRecips = TRUE; WORD wSubjectID = GENERAL_SUBJECT; LPWSTR wszSubject = NULL; LPWSTR wszStop = NULL; DWORD cbSubject = 0; LPSTR szSubject = NULL; LPSTR szSubjectCurrent = NULL; if (!fLanguageAvailable(LangID)) { //Use default of server LangID = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL); } hr = pdsnbuff->HrWriteBuffer((BYTE *) SUBJECT_HEADER, sizeof(SUBJECT_HEADER)-1); if (FAILED(hr)) goto Exit; //Set conversion context to UTF7 for RFC1522 subject pdsnbuff->SetConversionContext(&utf7convSubject); //Write subject with useful info if (((DSN_ACTION_FAILURE | DSN_ACTION_FAILURE_ALL) & dwDSNType) == dwDSNType) wSubjectID = FAILURE_SUBJECT; else if (DSN_ACTION_RELAYED == dwDSNType) wSubjectID = RELAY_SUBJECT; else if (DSN_ACTION_DELAYED == dwDSNType) wSubjectID = DELAY_SUBJECT; else if (DSN_ACTION_DELIVERED == dwDSNType) wSubjectID = DELIVERED_SUBJECT; else if (DSN_ACTION_EXPANDED == dwDSNType) wSubjectID = EXPANDED_SUBJECT; hr = pdsnbuff->HrWriteResource(wSubjectID, LangID); if (FAILED(hr)) goto Exit; //Write *English* subject for Queue Admin/Message tracking //Use english, becuase we return a ASCII string to queue admin hr = pdsnbuff->HrLoadResourceString(wSubjectID, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), &wszSubject, &cbSubject); if (FAILED(hr)) { ErrorTrace((LPARAM) this, "Unable to get resource for english subject 0x%08X", hr); } else { //We need to convert from UNICODE to ASCII... remember resource is not //NULL terminated szSubject = (LPSTR) pvMalloc(cbSubject/sizeof(WCHAR) + 1); wszStop = wszSubject + (cbSubject/sizeof(WCHAR)); if (szSubject) { szSubjectCurrent = szSubject; while ((wszSubject < wszStop) && *wszSubject) { wctomb(szSubjectCurrent, *wszSubject); szSubjectCurrent++; wszSubject++; } *szSubjectCurrent = '\0'; pIMailMsgPropertiesDSN->PutStringA(IMMPID_MP_RFC822_MSG_SUBJECT, szSubject); FreePv(szSubject); } } pdsnbuff->ResetConversionContext(); hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; //write summary saying that this is a MIME message hr = pdsnbuff->HrWriteBuffer((BYTE *) MESSAGE_SUMMARY, sizeof(MESSAGE_SUMMARY)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) MIME_DELIMITER, sizeof(MIME_DELIMITER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; //Write content type hr = pdsnbuff->HrWriteBuffer((BYTE *) MIME_CONTENT_TYPE, sizeof(MIME_CONTENT_TYPE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_HUMAN_READABLE_TYPE, sizeof(DSN_HUMAN_READABLE_TYPE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_MIME_CHARSET_HEADER, sizeof(DSN_MIME_CHARSET_HEADER)-1); if (FAILED(hr)) goto Exit; //For now... we do our encoding as UTF7.... put that as the charset hr = pdsnbuff->HrWriteBuffer((BYTE *) UTF7_CHARSET, sizeof(UTF7_CHARSET)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; //Set conversion context to UTF7 pdsnbuff->SetConversionContext(&utf7conv); hr = pdsnbuff->HrWriteResource(DSN_SUMMARY, LangID); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; //Describe the type of DSN if (((DSN_ACTION_FAILURE | DSN_ACTION_FAILURE_ALL) & dwDSNType) == dwDSNType) { //See if we have a failure-specific message switch(hrStatus) { #ifdef NEVER //CAT can generate errors other than unresolved recipeints //We will use the generic DSN failure message rather than confuse //recipients case CAT_W_SOME_UNDELIVERABLE_MSGS: hr = pdsnbuff->HrWriteResource(FAILURE_SUMMARY_MAILBOX, LangID); break; #endif //NEVER case AQUEUE_E_MAX_HOP_COUNT_EXCEEDED: hr = pdsnbuff->HrWriteResource(FAILURE_SUMMARY_HOP, LangID); break; case AQUEUE_E_MSG_EXPIRED: case AQUEUE_E_HOST_NOT_RESPONDING: case AQUEUE_E_CONNECTION_DROPPED: hr = pdsnbuff->HrWriteResource(FAILURE_SUMMARY_EXPIRE, LangID); break; default: hr = pdsnbuff->HrWriteResource(FAILURE_SUMMARY, LangID); } if (FAILED(hr)) goto Exit; } else if (DSN_ACTION_RELAYED == dwDSNType) { hr = pdsnbuff->HrWriteResource(RELAY_SUMMARY, LangID); if (FAILED(hr)) goto Exit; } else if (DSN_ACTION_DELAYED == dwDSNType) { //UE want this three line warning. hr = pdsnbuff->HrWriteResource(DELAY_WARNING, LangID); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteResource(DELAY_DO_NOT_SEND, LangID); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteResource(DELAY_SUMMARY, LangID); if (FAILED(hr)) goto Exit; } else if (DSN_ACTION_DELIVERED == dwDSNType) { hr = pdsnbuff->HrWriteResource(DELIVERED_SUMMARY, LangID); if (FAILED(hr)) goto Exit; } else if (DSN_ACTION_EXPANDED == dwDSNType) { hr = pdsnbuff->HrWriteResource(EXPANDED_SUMMARY, LangID); if (FAILED(hr)) goto Exit; } else { //In retail this will cause an extra blank line to appear in the DSN, _ASSERT(0 && "Unsupported DSN Action"); fWriteRecips = FALSE; } //Write a list of recipients for this DSN if (fWriteRecips) { hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; hr = HrWriteHumanReadableListOfRecips(pIMailMsgRecipients, prpfctxt, dwDSNType, pdsnbuff); hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; } //Extra space to have nicer formatting in Outlook 97. hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-1); if (FAILED(hr)) goto Exit; //Reset resource conversion context to default pdsnbuff->ResetConversionContext(); Exit: TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrWriteDSNReportPerMsgProperties ]--------------------- // // // Description: // Write the per-msg portion of the DSN Report // Parameters: // pIMailMsgProperties IMailMsgProperties to generate DSN for // pdsnbuff CDSNBuffer to write content to // szReportingMTA MTA requesting DSN // cbReportingMTA String length of reporting MTA // szMimeBoundary MIME boundary for this message // cbMimeBoundary Length of MIME boundary // Returns: // S_OK on success // History: // 7/6/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteDSNReportPerMsgProperties( IN IMailMsgProperties *pIMailMsgProperties, IN CDSNBuffer *pdsnbuff, IN LPSTR szReportingMTA, IN DWORD cbReportingMTA, IN LPSTR szMimeBoundary, IN DWORD cbMimeBoundary) { HRESULT hr = S_OK; CHAR szPropBuffer[PROP_BUFFER_SIZE]; _ASSERT(szReportingMTA && cbReportingMTA); //Write properly formatted MIME boundary and report type hr = pdsnbuff->HrWriteBuffer((BYTE *) MIME_DELIMITER, sizeof(MIME_DELIMITER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) MIME_CONTENT_TYPE, sizeof(MIME_CONTENT_TYPE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_MIME_TYPE, sizeof(DSN_MIME_TYPE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-1); if (FAILED(hr)) goto Exit; //Write DSN_HEADER_ENVID if we have it hr = pIMailMsgProperties->GetStringA(IMMPID_MP_DSN_ENVID_VALUE, PROP_BUFFER_SIZE, szPropBuffer); if (SUCCEEDED(hr)) { //Prop found hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_HEADER_ENVID, sizeof(DSN_HEADER_ENVID)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szPropBuffer, lstrlen(szPropBuffer)); if (FAILED(hr)) goto Exit; } else { if (MAILMSG_E_PROPNOTFOUND == hr) hr = S_OK; else goto Exit; } hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_HEADER_REPORTING_MTA, sizeof(DSN_HEADER_REPORTING_MTA)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szReportingMTA, cbReportingMTA); if (FAILED(hr)) goto Exit; //Write DSN_HEADER_RECEIVED_FROM if we have it hr = pIMailMsgProperties->GetStringA(IMMPID_MP_HELO_DOMAIN, PROP_BUFFER_SIZE, szPropBuffer); if (SUCCEEDED(hr)) { //Prop found hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_HEADER_RECEIVED_FROM, sizeof(DSN_HEADER_RECEIVED_FROM)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szPropBuffer, lstrlen(szPropBuffer)); if (FAILED(hr)) goto Exit; } else { if (MAILMSG_E_PROPNOTFOUND == hr) hr = S_OK; else goto Exit; } //Write DSN_HEADER_ARRIVAL_DATE if we have it hr = pIMailMsgProperties->GetStringA(IMMPID_MP_ARRIVAL_TIME, PROP_BUFFER_SIZE, szPropBuffer); if (SUCCEEDED(hr)) { //Prop found hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_HEADER_ARRIVAL_DATE, sizeof(DSN_HEADER_ARRIVAL_DATE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szPropBuffer, lstrlen(szPropBuffer)); if (FAILED(hr)) goto Exit; } else { if (MAILMSG_E_PROPNOTFOUND == hr) hr = S_OK; else goto Exit; } Exit: return hr; } //---[ CDefaultDSNSink::HrWriteDSNReportPreRecipientProperties ]--------------- // // // Description: // Write a per-recipient portion of the DSN Report // Parameters: // pIMailMsgRecipients IMailMsgProperties that DSN is being generated for // pdsnbuff CDSNBuffer to write content to // dwRecipFlags Recipient flags that we be we for this recipient // iRecip Recipient to generate report for // szExpireTime Time (if known) when message expires // cbExpireTime size of string // dwDSNAction DSN Action to take for this recipient // dwRFC821Status Global RFC821 status DWORD // hrStatus Global HRESULT status // Returns: // S_OK on success // History: // 7/6/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteDSNReportPreRecipientProperties( IN IMailMsgRecipients *pIMailMsgRecipients, IN CDSNBuffer *pdsnbuff, IN DWORD dwRecipFlags, IN DWORD iRecip, IN LPSTR szExpireTime, IN DWORD cbExpireTime, IN DWORD dwDSNAction, IN DWORD dwRFC821Status, IN HRESULT hrStatus) { HRESULT hr = S_OK; CHAR szBuffer[PROP_BUFFER_SIZE]; CHAR szStatus[STATUS_STRING_SIZE]; BOOL fFoundDiagnostic = FALSE; CHAR szAddressType[PROP_BUFFER_SIZE]; //Write blank line between recipient reports (recip fields start with \n) hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-1); if (FAILED(hr)) goto Exit; //Write DSN_RP_HEADER_ORCPT if we have it hr = pIMailMsgRecipients->GetStringA(iRecip, IMMPID_RP_DSN_ORCPT_VALUE, PROP_BUFFER_SIZE, szBuffer); if (S_OK == hr) //prop was found { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_ORCPT, sizeof(DSN_RP_HEADER_ORCPT)-1); if (FAILED(hr)) goto Exit; //write address value - type should be included in this property hr = pdsnbuff->HrWriteBuffer((BYTE *) szBuffer, lstrlen(szBuffer)); if (FAILED(hr)) goto Exit; } else if (FAILED(hr)) { if (MAILMSG_E_PROPNOTFOUND == hr) hr = S_OK; else goto Exit; } //Write DSN_RP_HEADER_FINAL_RECIP hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_FINAL_RECIP, sizeof(DSN_RP_HEADER_FINAL_RECIP)-1); if (FAILED(hr)) goto Exit; //Check for IMMPID_RP_DSN_PRE_CAT_ADDRESS first hr = pIMailMsgRecipients->GetStringA(iRecip, IMMPID_RP_DSN_PRE_CAT_ADDRESS, PROP_BUFFER_SIZE, szBuffer); if (S_OK == hr) //prop was found { //write address value - type should be included in this property hr = pdsnbuff->HrWriteBuffer((BYTE *) szBuffer, lstrlen(szBuffer)); if (FAILED(hr)) goto Exit; } else //we need to use IMMPID_RP_ADDRESS_SMTP instead { hr = HrGetRecipAddressAndType(pIMailMsgRecipients, iRecip, PROP_BUFFER_SIZE, szBuffer, sizeof(szAddressType), szAddressType); if (SUCCEEDED(hr)) { //write address type hr = pdsnbuff->HrWriteBuffer((BYTE *) szAddressType, lstrlen(szAddressType)); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_HEADER_TYPE_DELIMITER, sizeof(DSN_HEADER_TYPE_DELIMITER)-1); if (FAILED(hr)) goto Exit; //write address value hr = pdsnbuff->HrWriteBuffer((BYTE *) szBuffer, lstrlen(szBuffer)); if (FAILED(hr)) goto Exit; } else { _ASSERT(SUCCEEDED(hr) && "Recipient address *must* be present"); } } //Write DSN_RP_HEADER_ACTION hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_ACTION, sizeof(DSN_RP_HEADER_ACTION)-1); if (FAILED(hr)) goto Exit; if (dwDSNAction & DSN_ACTION_FAILURE) { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_ACTION_FAILURE, sizeof(DSN_RP_HEADER_ACTION_FAILURE)-1); if (FAILED(hr)) goto Exit; } else if (dwDSNAction & DSN_ACTION_DELAYED) { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_ACTION_DELAYED, sizeof(DSN_RP_HEADER_ACTION_DELAYED)-1); if (FAILED(hr)) goto Exit; } else if (dwDSNAction & DSN_ACTION_RELAYED) { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_ACTION_RELAYED, sizeof(DSN_RP_HEADER_ACTION_RELAYED)-1); if (FAILED(hr)) goto Exit; } else if (dwDSNAction & DSN_ACTION_DELIVERED) { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_ACTION_DELIVERED, sizeof(DSN_RP_HEADER_ACTION_DELIVERED)-1); if (FAILED(hr)) goto Exit; } else if (dwDSNAction & DSN_ACTION_EXPANDED) { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_ACTION_EXPANDED, sizeof(DSN_RP_HEADER_ACTION_EXPANDED)-1); if (FAILED(hr)) goto Exit; } else { _ASSERT(0 && "No DSN Action requested"); } //Write DSN_RP_HEADER_STATUS hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_STATUS, sizeof(DSN_RP_HEADER_STATUS)-1); if (FAILED(hr)) goto Exit; //Get status code hr = HrGetStatusCode(pIMailMsgRecipients, iRecip, dwDSNAction, dwRFC821Status, hrStatus, dwRecipFlags, PROP_BUFFER_SIZE, szBuffer, szStatus); if (FAILED(hr)) goto Exit; if (S_OK == hr) { //found diagnostic code fFoundDiagnostic = TRUE; } hr = pdsnbuff->HrWriteBuffer((BYTE *) szStatus, lstrlen(szStatus)); if (FAILED(hr)) goto Exit; if (fFoundDiagnostic) { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_DIAG_CODE, sizeof(DSN_RP_HEADER_DIAG_CODE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szBuffer, lstrlen(szBuffer)); if (FAILED(hr)) goto Exit; } //Write DSN_RP_HEADER_RETRY_UNTIL using expire time if delay if (szExpireTime && (DSN_ACTION_DELAYED & dwDSNAction)) { hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RP_HEADER_RETRY_UNTIL, sizeof(DSN_RP_HEADER_RETRY_UNTIL)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szExpireTime, cbExpireTime); if (FAILED(hr)) goto Exit; } Exit: return hr; } //---[ CDefaultDSNSink::HrWriteDSNClosingAndOriginalMessage ]------------------ // // // Description: // Writes the closing of the DSN as well as the end of the // Parameters: // pIMailMsgProperties IMailMsgProperties to generate DSN for // pIMailMsgPropertiesDSN IMailMsgProperties for DSN // pdsnbuff CDSNBuffer to write content to // pDestFile PFIO_CONTEXT for destination file // dwDSNAction DSN actions for this DSN // szMimeBoundary MIME boundary for this message // cbMimeBoundary Length of MIME boundary // Returns: // // History: // 7/6/98 - MikeSwa Created // 1/6/2000 - MikeSwa Modified to add RET=HDRS support // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteDSNClosingAndOriginalMessage( IN IMailMsgProperties *pIMailMsgProperties, IN IMailMsgProperties *pIMailMsgPropertiesDSN, IN CDSNBuffer *pdsnbuff, IN PFIO_CONTEXT pDestFile, IN DWORD dwDSNAction, IN LPSTR szMimeBoundary, IN DWORD cbMimeBoundary) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrWriteDSNClosingAndOriginalMessage"); HRESULT hr = S_OK; BOOL fHeadersOnly = FALSE; BOOL fRetSpecified = FALSE; CHAR szRET[] = "FULL"; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) MIME_DELIMITER, sizeof(MIME_DELIMITER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; //Write Body content type MIME_CONTENT_TYPE = rfc822 hr = pdsnbuff->HrWriteBuffer((BYTE *) MIME_CONTENT_TYPE, sizeof(MIME_CONTENT_TYPE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_RFC822_TYPE, sizeof(DSN_RFC822_TYPE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; //Determine if we want to return the full message or minimal headers. //The logic for this is: // - Obey explicit RET (IMMPID_MP_DSN_RET_VALUE) values // - Default to HDRS for all non-error DSNs // - Default to FULL on errors hr = pIMailMsgProperties->GetStringA(IMMPID_MP_DSN_RET_VALUE, sizeof(szRET), szRET); if (SUCCEEDED(hr)) { fRetSpecified = TRUE; if(!_strnicmp(szRET, (char * )"FULL", 4)) fHeadersOnly = FALSE; else if (!_strnicmp(szRET, (char * )"HDRS", 4)) fHeadersOnly = TRUE; else fRetSpecified = FALSE; } else hr = S_OK; //treat has if property not specified if (!fRetSpecified) { if ((DSN_ACTION_FAILURE | DSN_ACTION_FAILURE_ALL | DSN_ACTION_DELAYED) & dwDSNAction) fHeadersOnly = FALSE; else fHeadersOnly = TRUE; } // // $$NOTE$$ // Due to interop problems, we are removing support for HDRS, since // it is causing malformed DSNs to appear. // fHeadersOnly = FALSE; if (fHeadersOnly) { hr = HrWriteOriginalMessageHeaders(pIMailMsgProperties, pIMailMsgPropertiesDSN, pdsnbuff, pDestFile, szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; } else { hr = HrWriteOriginalMessageFull(pIMailMsgProperties, pIMailMsgPropertiesDSN, pdsnbuff, pDestFile, szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; } Exit: TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrWriteOriginalMessageFull ]--------------------------- // // // Description: // Writes the entire original message to the DSN // Parameters: // pIMailMsgProperties IMailMsgProperties to generate DSN for // pIMailMsgPropertiesDSN IMailMsgProperties for DSN // pdsnbuff CDSNBuffer to write content to // pDestFile PFIO_CONTEXT for destination file // szMimeBoundary MIME boundary for this message // cbMimeBoundary Length of MIME boundary // Returns: // S_OK on success // History: // 1/6/2000 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteOriginalMessageFull( IN IMailMsgProperties *pIMailMsgProperties, IN IMailMsgProperties *pIMailMsgPropertiesDSN, IN CDSNBuffer *pdsnbuff, IN PFIO_CONTEXT pDestFile, IN LPSTR szMimeBoundary, IN DWORD cbMimeBoundary) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrWriteOriginalMessageFull"); HRESULT hr = S_OK; DWORD dwFileSize = 0; DWORD dwOrigMsgSize = 0; DWORD dwDontCare = 0; //Get the content size, so we know were we can start writing at hr = pIMailMsgProperties->GetContentSize(&dwOrigMsgSize, NULL); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrSeekForward(dwOrigMsgSize, &dwFileSize); if (FAILED(hr)) goto Exit; //Set size hint property on DSN for Queue Admin/Message Tracking hr = pIMailMsgPropertiesDSN->PutDWORD(IMMPID_MP_MSG_SIZE_HINT, dwOrigMsgSize + dwFileSize); if (FAILED(hr)) { //We really don't care too much about a failure with this ErrorTrace((LPARAM) this, "Error writing size hint 0x%08X", hr); hr = S_OK; } //Write at end of file - *before* file handle is lost to IMailMsg, hr = HrWriteMimeClosing(pdsnbuff, szMimeBoundary, cbMimeBoundary, &dwDontCare); if (FAILED(hr)) goto Exit; //write body hr = pIMailMsgProperties->CopyContentToFileAtOffset(pDestFile, dwFileSize, NULL); if (FAILED(hr)) goto Exit; Exit: TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrWriteOriginalMessageHeaders ]------------------------ // // // Description: // Writes only the headers of the original message to the DSN // Parameters: // pIMailMsgProperties IMailMsgProperties to generate DSN for // pIMailMsgPropertiesDSN IMailMsgProperties for DSN // pdsnbuff CDSNBuffer to write content to // pDestFile PFIO_CONTEXT for destination file // szMimeBoundary MIME boundary for this message // cbMimeBoundary Length of MIME boundary // Returns: // S_OK on success // History: // 1/6/2000 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteOriginalMessageHeaders( IN IMailMsgProperties *pIMailMsgProperties, IN IMailMsgProperties *pIMailMsgPropertiesDSN, IN CDSNBuffer *pdsnbuff, IN PFIO_CONTEXT pDestFile, IN LPSTR szMimeBoundary, IN DWORD cbMimeBoundary) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrWriteOriginalMessageHeaders"); HRESULT hr = S_OK; DWORD dwFileSize = 0; CHAR szPropBuffer[1026] = ""; DWORD cbPropSize = 0; //Loop through the 822 properties that we care about and write them //to the message. A truely RFC-compliant version would re-parse the //messages... and return all the headers // // From header // hr = pIMailMsgProperties->GetProperty(IMMPID_MP_RFC822_FROM_ADDRESS, sizeof(szPropBuffer), &cbPropSize, (PBYTE) szPropBuffer); if (SUCCEEDED(hr)) { hr = pdsnbuff->HrWriteBuffer((PBYTE)DSN_FROM_HEADER_NO_CRLF, sizeof(DSN_FROM_HEADER_NO_CRLF)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((PBYTE)szPropBuffer, cbPropSize-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-1); if (FAILED(hr)) goto Exit; } // // Message ID // hr = pIMailMsgProperties->GetProperty(IMMPID_MP_RFC822_MSG_ID, sizeof(szPropBuffer), &cbPropSize, (PBYTE) szPropBuffer); if (SUCCEEDED(hr)) { hr = pdsnbuff->HrWriteBuffer((PBYTE)MSGID_HEADER_NO_CRLF, sizeof(MSGID_HEADER_NO_CRLF)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((PBYTE)szPropBuffer, cbPropSize-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-1); if (FAILED(hr)) goto Exit; } // // Subject header // hr = pIMailMsgProperties->GetProperty(IMMPID_MP_RFC822_MSG_SUBJECT, sizeof(szPropBuffer), &cbPropSize, (PBYTE)szPropBuffer); if (SUCCEEDED(hr)) { hr = pdsnbuff->HrWriteBuffer((PBYTE)SUBJECT_HEADER_NO_CRLF, sizeof(SUBJECT_HEADER_NO_CRLF)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((PBYTE)szPropBuffer, cbPropSize-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-1); if (FAILED(hr)) goto Exit; } hr = HrWriteMimeClosing(pdsnbuff, szMimeBoundary, cbMimeBoundary, &dwFileSize); if (FAILED(hr)) goto Exit; //Set size hint property on DSN for Queue Admin/Message Tracking hr = pIMailMsgPropertiesDSN->PutDWORD(IMMPID_MP_MSG_SIZE_HINT, dwFileSize); if (FAILED(hr)) { //We really don't care too much about a failure with this ErrorTrace((LPARAM) this, "Error writing size hint 0x%08X", hr); hr = S_OK; } Exit: TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrWriteMimeClosing ]----------------------------------- // // // Description: // Write the MIME closing of the DSN after the 3rd MIME part. // Parameters: // pdsnbuff CDSNBuffer to write content to // szReportingMTA MTA requesting DSN // cbReportingMTA String length of reporting MTA // szMimeBoundary MIME boundary for this message // cbMimeBoundary Length of MIME boundary // Returns: // S_OK on success // Failure code from CDSNBuffer on failure // History: // 1/6/2000 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteMimeClosing( IN CDSNBuffer *pdsnbuff, IN LPSTR szMimeBoundary, IN DWORD cbMimeBoundary, OUT DWORD *pcbDSNSize) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrWriteMimeClosing"); HRESULT hr = S_OK; hr = pdsnbuff->HrWriteBuffer((BYTE *) BLANK_LINE, sizeof(BLANK_LINE)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) MIME_DELIMITER, sizeof(MIME_DELIMITER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szMimeBoundary, cbMimeBoundary); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) MIME_DELIMITER, sizeof(MIME_DELIMITER)-1); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-1); if (FAILED(hr)) goto Exit; //flush buffers hr = pdsnbuff->HrFlushBuffer(pcbDSNSize); if (FAILED(hr)) goto Exit; Exit: TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrGetStatusCode ]--------------------------------------- // // // Description: // Determines the status code (and diagnostic code) for a recipient. Will // check the following (in order) to determine the status code to return: // IMMPID_RP_SMTP_STATUS_STRING (per-recipient diagnostic code) // Combination of: // dwRecipFlags (determine who set the error) // IMMPID_RP_ERROR_CODE (per-recipient HRESULT error code) // dwDSNAction - kind of DSN being sent // Combination of: // dwRecipFlags (determine who set the error) // dwRFC821Status - per message status code // dwDSNAction - kind of DSN being sent // Combination of: // dwRecipFlags (determine who set the error) // hrStatus - per message HRESULT failure // dwDSNAction - kind of DSN being sent // Status codes are defined in RFC 1893 as follows: // status-code = class "." subject "." detail // class = "2"/"4"/"5" // subject = 1*3digit // detail = 1*3digit // // Additionally, the class of "2", "4", and "5" correspond to success, // transient failure, and hard failure respectively // Parameters: // pIMailMsgRecipients IMailMsgRecipients of message being DSN'd // iRecip The index of the recipient we are looking at // dwDSNAction The action code returned by fdwGetDSNAction // dwRFC821Status RFC821 Status code returned by SMTP // hrStatus HRESULT error if SMTP status is unavailable // dwRecipFlags Per-recipient flags for this recipient // cbExtendedStatus Size of buffer for diagnostic code // szExtendedStatus Buffer for diagnostic code // szStatus Buffer for "n.n.n" formatted status code // Returns: // S_OK Success - found diagnostic code as well // S_FALSE Success - but no diagnostic code // History: // 7/6/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrGetStatusCode( IN IMailMsgRecipients *pIMailMsgRecipients, IN DWORD iRecip, IN DWORD dwDSNAction, IN DWORD dwRFC821Status, IN HRESULT hrStatus, IN DWORD dwRecipFlags, IN DWORD cbExtendedStatus, IN OUT LPSTR szExtendedStatus, IN OUT CHAR szStatus[STATUS_STRING_SIZE]) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrGetStatusCode"); HRESULT hr = S_OK; HRESULT hrPerRecipStatus = S_OK; BOOL fFoundDiagnostic = FALSE; //Check for IMMPID_RP_SMTP_STATUS_STRING on recipient and try to get //status code from there hr = pIMailMsgRecipients->GetStringA(iRecip, IMMPID_RP_SMTP_STATUS_STRING, cbExtendedStatus, szExtendedStatus); if (SUCCEEDED(hr)) //prop was found { fFoundDiagnostic = TRUE; hr = HrGetStatusFromStatus(cbExtendedStatus, szExtendedStatus, szStatus); if (S_OK == hr) goto Exit; else if (S_FALSE == hr) hr = S_OK; //not really an error... just get code from someplace else else goto Exit; //other failure } else if (MAILMSG_E_PROPNOTFOUND == hr) { //Not really a hard error _ASSERT(!fFoundDiagnostic); hr = S_OK; } else { goto Exit; } //Get Per Recipient HRESULT DEBUG_DO_IT(hrPerRecipStatus = 0xFFFFFFFF); hr = pIMailMsgRecipients->GetDWORD(iRecip, IMMPID_RP_ERROR_CODE, (DWORD *) &hrPerRecipStatus); if (SUCCEEDED(hr)) { _ASSERT((0xFFFFFFFF != hrPerRecipStatus) && "Property not returned by MailMsg!!!"); hr = HrGetStatusFromContext(hrPerRecipStatus, dwRecipFlags, dwDSNAction, szStatus); if (FAILED(hr)) goto Exit; } else { //There is no per-recip status if (MAILMSG_E_PROPNOTFOUND != hr) goto Exit; hr = HrGetStatusFromRFC821Status(dwRFC821Status, szStatus); if (FAILED(hr)) goto Exit; if (S_OK == hr) //got status code from dwRFC821Status goto Exit; //If all else fails Get status code using global HRESULT & context hr = HrGetStatusFromContext(hrStatus, dwRecipFlags, dwDSNAction, szStatus); if (FAILED(hr)) goto Exit; } Exit: if (SUCCEEDED(hr)) { if (fFoundDiagnostic) hr = S_OK; else hr = S_FALSE; } TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrGetStatusFromStatus ]-------------------------------- // // // Description: // Parse status code from RFC2034 extended status code string // // If string is not a complete RFC2034 extended status string, this // function will attempt to parse the RFC821 SMTP return code and // turn it into an extended status string. // Parameters: // IN cbExtendedStatus Size of extended status buffer // IN szExtendedStatus Extended status buffer // IN OUT szStatus RFC1893 formatted status code // Returns: // S_OK on success // S_FALSE if could not be parsed // FAILED if other error occurs // History: // 7/7/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrGetStatusFromStatus( IN DWORD cbExtendedStatus, IN OUT LPSTR szExtendedStatus, IN OUT CHAR szStatus[STATUS_STRING_SIZE]) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrGetStatusFromStatus"); HRESULT hr = S_OK; DWORD dwRFC821Status = 0; BOOL fFormattedCorrectly = FALSE; CHAR *pchStatus = NULL; CHAR *pchDiag = NULL; //ptr to status string supplied by SMTP DWORD cNumDigits = 0; int i = 0; //copy status code from diagnostic string in to status code pchStatus = szStatus; pchDiag = szExtendedStatus; //there must be at least 3 characters to attempt parsing if (cbExtendedStatus < MIN_CHAR_FOR_VALID_RFC821) { hr = S_FALSE; goto Exit; } //check RFC822 if (!((DSN_STATUS_CH_CLASS_SUCCEEDED == *pchDiag) || (DSN_STATUS_CH_CLASS_TRANSIENT == *pchDiag) || (DSN_STATUS_CH_CLASS_FAILED == *pchDiag))) { //Doesn't start with RFC822... can't be valid hr = S_FALSE; goto Exit; } //RFC2034 must have at least RFC822 + " " + "x.x.x" = 10 chanracters if (cbExtendedStatus >= MIN_CHAR_FOR_VALID_RFC2034) { pchDiag += MIN_CHAR_FOR_VALID_RFC821; //format is "xxx x.x.x" //Find first digit while(isspace(*pchDiag) && pchDiag < (szExtendedStatus + cbExtendedStatus)) pchDiag++; if ((DSN_STATUS_CH_CLASS_SUCCEEDED == *pchDiag) || (DSN_STATUS_CH_CLASS_TRANSIENT == *pchDiag) || (DSN_STATUS_CH_CLASS_FAILED == *pchDiag)) { //copy status code class *pchStatus = *pchDiag; pchStatus++; pchDiag++; //Next character must be a DSN_STATUS_CH_DELIMITER if (DSN_STATUS_CH_DELIMITER == *pchDiag) { *pchStatus = DSN_STATUS_CH_DELIMITER; pchStatus++; pchDiag++; //now parse this 1*3digit "." 1*3digit part for (i = 0; i < 3; i++) { *pchStatus = *pchDiag; if (!isdigit(*pchDiag)) { if (DSN_STATUS_CH_DELIMITER != *pchDiag) { fFormattedCorrectly = FALSE; break; } //copy delimiter *pchStatus = *pchDiag; pchStatus++; pchDiag++; break; } pchStatus++; pchDiag++; fFormattedCorrectly = TRUE; //hace first digit } if (fFormattedCorrectly) //so far.. so good { fFormattedCorrectly = FALSE; for (i = 0; i < 3; i++) { *pchStatus = *pchDiag; if (!isdigit(*pchDiag)) { if (!isspace(*pchDiag)) { fFormattedCorrectly = FALSE; break; } break; } pchStatus++; pchDiag++; fFormattedCorrectly = TRUE; } //If we have found a good status code... go to exit if (fFormattedCorrectly) { *pchStatus = '\0'; //make sure last CHAR is a NULL goto Exit; } } } } } //We haven't been able to parse the extended status code, but we //know we have at least a valid RFC822 response string //convert to DWORD for (i = 0; i < MIN_CHAR_FOR_VALID_RFC821; i++) { dwRFC821Status *= 10; dwRFC821Status += szExtendedStatus[i] - '0'; } hr = HrGetStatusFromRFC821Status(dwRFC821Status, szStatus); _ASSERT(S_OK == hr); //this cannot possibly fail //The code *should* be valid at this point _ASSERT((DSN_STATUS_CH_CLASS_SUCCEEDED == szStatus[0]) || (DSN_STATUS_CH_CLASS_TRANSIENT == szStatus[0]) || (DSN_STATUS_CH_CLASS_FAILED == szStatus[0])); hr = S_OK; Exit: TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrGetStatusFromContext ]-------------------------------- // // // Description: // Determine status based on supplied context information // Parameters: // hrRecipient HRESULT for this recipient // dwRecipFlags Flags for this recipient // dwDSNAction DSN Action for this recipient // szStatus Buffer to return status in // Returns: // S_OK Was able to get a valid status code // History: // 7/7/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrGetStatusFromContext( IN HRESULT hrRecipient, IN DWORD dwRecipFlags, IN DWORD dwDSNAction, IN OUT CHAR szStatus[STATUS_STRING_SIZE]) { HRESULT hr = S_OK; BOOL fValidHRESULT = FALSE; BOOL fRecipContext = FALSE; int iStatus = 0; int i = 0; CHAR chStatusClass = DSN_STATUS_CH_INVALID; CHAR rgchStatusSubject[3] = {DSN_STATUS_CH_INVALID, DSN_STATUS_CH_INVALID, DSN_STATUS_CH_INVALID}; CHAR rgchStatusDetail[3] = {DSN_STATUS_CH_INVALID, DSN_STATUS_CH_INVALID, DSN_STATUS_CH_INVALID}; //check to make sure that HRESULT is set according to the type of DSN happening if (dwDSNAction & DSN_ACTION_FAILURE) { if (FAILED(hrRecipient)) //must be a failure code fValidHRESULT = TRUE; chStatusClass = DSN_STATUS_CH_CLASS_FAILED; } else if (dwDSNAction & DSN_ACTION_DELAYED) { if (FAILED(hrRecipient)) //must be a failure code fValidHRESULT = TRUE; chStatusClass = DSN_STATUS_CH_CLASS_TRANSIENT; } else if ((dwDSNAction & DSN_ACTION_RELAYED) || (dwDSNAction & DSN_ACTION_DELIVERED) || (dwDSNAction & DSN_ACTION_EXPANDED)) { if (SUCCEEDED(hrRecipient)) //must be a success code fValidHRESULT = TRUE; chStatusClass = DSN_STATUS_CH_CLASS_SUCCEEDED; } else { _ASSERT(0 && "No DSN Action specified"); } //special case HRESULTS if (fValidHRESULT) { switch (hrRecipient) { case CAT_E_GENERIC: // 5.1.0 - General Cat failure. case CAT_E_BAD_RECIPIENT: //5.1.0 - general bad address error rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_ADDRESS; rgchStatusDetail[0] = '0'; goto Exit; case CAT_E_ILLEGAL_ADDRESS: //5.1.3 - bad address syntax rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_ADDRESS; rgchStatusDetail[0] = '3'; goto Exit; case CAT_W_SOME_UNDELIVERABLE_MSGS: //5.1.1 - recipient could not be resolved case (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)): rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_ADDRESS; rgchStatusDetail[0] = '1'; goto Exit; case CAT_E_MULTIPLE_MATCHES: //5.1.4 - amiguous address rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_ADDRESS; rgchStatusDetail[0] = '4'; goto Exit; case AQUEUE_E_MAX_HOP_COUNT_EXCEEDED: //4.4.6 chStatusClass = DSN_STATUS_CH_CLASS_TRANSIENT; case CAT_E_FORWARD_LOOP: //5.4.6 rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_NETWORK; rgchStatusDetail[0] = '6'; goto Exit; case AQUEUE_E_LOOPBACK_DETECTED: //5.3.5 //server is configured to loop back on itself chStatusClass = DSN_STATUS_CH_CLASS_FAILED; rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_SYSTEM; rgchStatusDetail[0] = '5'; goto Exit; case AQUEUE_E_MSG_EXPIRED: //4.4.7 chStatusClass = DSN_STATUS_CH_CLASS_TRANSIENT; rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_NETWORK; rgchStatusDetail[0] = '7'; goto Exit; case AQUEUE_E_HOST_NOT_RESPONDING: //4.4.1 chStatusClass = DSN_STATUS_CH_CLASS_TRANSIENT; rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_NETWORK; rgchStatusDetail[0] = '1'; goto Exit; case AQUEUE_E_CONNECTION_DROPPED: //4.4.2 chStatusClass = DSN_STATUS_CH_CLASS_TRANSIENT; rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_NETWORK; rgchStatusDetail[0] = '2'; goto Exit; case AQUEUE_E_TOO_MANY_RECIPIENTS: //5.5.3 chStatusClass = DSN_STATUS_CH_CLASS_FAILED; rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_PROTOCOL; rgchStatusDetail[0] = '3'; goto Exit; case AQUEUE_E_LOCAL_MAIL_REFUSED: //5.2.1 chStatusClass = DSN_STATUS_CH_CLASS_FAILED; rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_MAILBOX; rgchStatusDetail[0] = '1'; goto Exit; case AQUEUE_E_MESSAGE_TOO_LARGE: //5.2.3 case AQUEUE_E_LOCAL_QUOTA_EXCEEDED: //5.2.3 chStatusClass = DSN_STATUS_CH_CLASS_FAILED; rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_MAILBOX; rgchStatusDetail[0] = '3'; goto Exit; case AQUEUE_E_ACCESS_DENIED: //5.7.1 case AQUEUE_E_SENDER_ACCESS_DENIED: //5.7.1 chStatusClass = DSN_STATUS_CH_CLASS_FAILED; rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_POLICY; rgchStatusDetail[0] = '1'; goto Exit; } } if ((RP_ERROR_CONTEXT_STORE | RP_ERROR_CONTEXT_CAT | RP_ERROR_CONTEXT_MTA) & dwRecipFlags) fRecipContext = TRUE; //Now look at the context on recipient flags //$$TODO - Use HRESULT's for these case as well if ((RP_ERROR_CONTEXT_STORE & dwRecipFlags) || (!fRecipContext && (DSN_ACTION_CONTEXT_STORE & dwDSNAction))) { rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_MAILBOX; rgchStatusDetail[0] = DSN_STATUS_CH_DETAIL_GENERAL; } else if ((RP_ERROR_CONTEXT_CAT & dwRecipFlags) || (!fRecipContext && (DSN_ACTION_CONTEXT_CAT & dwDSNAction))) { rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_ADDRESS; rgchStatusDetail[0] = DSN_STATUS_CH_DETAIL_GENERAL; } else if ((RP_ERROR_CONTEXT_MTA & dwRecipFlags) || (!fRecipContext && (DSN_ACTION_CONTEXT_MTA & dwDSNAction))) { rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_PROTOCOL; rgchStatusDetail[0] = DSN_STATUS_CH_DETAIL_GENERAL; } else { rgchStatusSubject[0] = DSN_STATUS_CH_SUBJECT_GENERAL; rgchStatusDetail[0] = DSN_STATUS_CH_DETAIL_GENERAL; } Exit: if (SUCCEEDED(hr)) { //compose szStatus _ASSERT(DSN_STATUS_CH_INVALID != chStatusClass); _ASSERT(DSN_STATUS_CH_INVALID != rgchStatusSubject[0]); _ASSERT(DSN_STATUS_CH_INVALID != rgchStatusDetail[0]); szStatus[iStatus] = chStatusClass; iStatus++; szStatus[iStatus] = DSN_STATUS_CH_DELIMITER; iStatus++; for (i = 0; (i < 3) && (DSN_STATUS_CH_INVALID != rgchStatusSubject[i]); i++) { szStatus[iStatus] = rgchStatusSubject[i]; iStatus++; } szStatus[iStatus] = DSN_STATUS_CH_DELIMITER; iStatus++; for (i = 0; (i < 3) && (DSN_STATUS_CH_INVALID != rgchStatusDetail[i]); i++) { szStatus[iStatus] = rgchStatusDetail[i]; iStatus++; } szStatus[iStatus] = '\0'; hr = S_OK; } return hr; } //---[ CDefaultDSNSink::HrGetStatusFromRFC821Status ]-------------------------- // // // Description: // Attempts to generate a DSN status code from a integer version of a // RFC821 response // Parameters: // IN dwRFC821Status Integer version of RFC821Status // IN OUT szStatus Buffer to write status string to // Returns: // S_OK if valid status that could be converted to dsn status code // S_FALSE if status code cannot be converted // History: // 7/9/98 - MikeSwa Created // // Note: // Eventually, there may be a way to pass extended information in the // DWORD to this event. We *could* also encode RFC1893 (x.xxx.xxx format) // in a DWORD (in dwRFC821Status) as follows: // // 0xF 0 000 000 // | | \-/ \-/ // | | | +----- detail portion of status code // | | +--------- subject portion of status code // | +------------ class portion of status code // +-------------- mask to distinguish from RFC821 status code // // For example "2.1.256" could be encoded as 0xF2001256 // // If we do this, we will probably need to expose public functions to // compress/uncompress. // // Yet another possiblity would be to expose an HRESULT facility "RFC1893" // Use success, warning, and failed bits to denote the class, and then // use the error code space to encode the status codes //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrGetStatusFromRFC821Status( IN DWORD dwRFC821Status, IN OUT CHAR szStatus[STATUS_STRING_SIZE]) { HRESULT hr = S_OK; //For now, there will be a very simple implementation just converts //to 2.0.0, 4.0.0, or 5.0.0, but this function is designed to be //the central place that converts RFC821 status codes to DSN (RFC1893) //status codes _ASSERT((!dwRFC821Status) || (((200 <= dwRFC821Status) && (299 >= dwRFC821Status)) || ((400 <= dwRFC821Status) && (599 >= dwRFC821Status))) && "Invalid Status Code"); //For now have simplistic mapping of RFC821 status codes if ((200 <= dwRFC821Status) && (299 >= dwRFC821Status)) //200 level error { strcpy(szStatus, DSN_STATUS_SUCCEEDED); } else if ((400 <= dwRFC821Status) && (499 >= dwRFC821Status)) //400 level error { strcpy(szStatus, DSN_STATUS_DELAYED); } else if ((500 <= dwRFC821Status) && (599 >= dwRFC821Status)) //500 level error { strcpy(szStatus, DSN_STATUS_FAILED); } else { hr = S_FALSE; } return hr; } //---[ FileTimeToLocalRFC822Date ]--------------------------------------------- // // // Description: // Converts filetime to RFC822 compliant date // Parameters: // ft Filetime to generate date for // achReturn Buffer for filetime // Returns: // - // History: // 8/19/98 - MikeSwa Modified from various timeconv.cxx functions written // by Lindsay Harris - lindasyh // Carl Kadie [carlk] // //----------------------------------------------------------------------------- void FileTimeToLocalRFC822Date(const FILETIME & ft, char achReturn[MAX_RFC822_DATE_SIZE]) { FILETIME ftLocal; SYSTEMTIME st; char chSign; // Sign to print. DWORD dwResult; int iBias; // Offset relative to GMT. TIME_ZONE_INFORMATION tzi; // Local time zone data. dwResult = GetTimeZoneInformation( &tzi ); _ASSERT(achReturn); //real assert FileTimeToLocalFileTime(&ft, &ftLocal); FileTimeToSystemTime(&ftLocal, &st); // Calculate the time zone offset. iBias = tzi.Bias; if( dwResult == TIME_ZONE_ID_DAYLIGHT ) iBias += tzi.DaylightBias; /* * We always want to print the sign for the time zone offset, so * we decide what it is now and remember that when converting. * The convention is that west of the 0 degree meridian has a * negative offset - i.e. add the offset to GMT to get local time. */ if( iBias > 0 ) { chSign = '-'; // Yes, I do mean negative. } else { iBias = -iBias; chSign = '+'; } /* * No major trickery here. We have all the data, so simply * format it according to the rules on how to do this. */ wsprintf( achReturn, "%s, %d %s %04d %02d:%02d:%02d %c%02d%02d", s_rgszWeekDays[st.wDayOfWeek], st.wDay, s_rgszMonth[ st.wMonth - 1 ], st.wYear, st.wHour, st.wMinute, st.wSecond, chSign, (iBias / 60) % 100, iBias % 60 ); _ASSERT(lstrlen(achReturn) < MAX_RFC822_DATE_SIZE); } //---[ CDefaultDSNSink::GetCurrentIterationsDSNAction ]------------------------ // // // Description: // This function will select one of the pdwActualDSNAction to generate // DSNs on during this call to the DSN generation sink. // Parameters: // IN OUT pdwActionDSNAction DSN Actions that can/will be used. // IN OUT pcIterationsLeft Approximate # of times needed to call // DSN generation // Returns: // - // History: // 12/14/98 - MikeSwa Created // //----------------------------------------------------------------------------- void CDefaultDSNSink::GetCurrentIterationsDSNAction( IN OUT DWORD *pdwActualDSNAction, IN OUT DWORD *pcIterationsLeft) { _ASSERT(pdwActualDSNAction); _ASSERT(pcIterationsLeft); const DWORD MAX_DSN_ACTIONS = 6; //In the following array FAILURE_ALL must come first or else we will //generate separate failure DSNs for hard failures and undelivereds. DWORD rgPossibleDSNActions[MAX_DSN_ACTIONS] = {DSN_ACTION_FAILURE_ALL, DSN_ACTION_FAILURE, DSN_ACTION_DELAYED, DSN_ACTION_RELAYED, DSN_ACTION_DELIVERED, DSN_ACTION_EXPANDED}; DWORD i = 0; DWORD iLastMatch = MAX_DSN_ACTIONS; DWORD iFirstMatch = MAX_DSN_ACTIONS; DWORD iStartIndex = 0; //Since the possible DSNs to generate may change from call to call (because //we are updating the pre-recipient flags), we need to generate and maintain //pcIterationsLeft based on the possible Actions (which will not be changing //from call to call). _ASSERT(*pcIterationsLeft < MAX_DSN_ACTIONS); //Figure out where we should start if this is not the if (*pcIterationsLeft) iStartIndex = MAX_DSN_ACTIONS-*pcIterationsLeft; //Loop through possible DSN actions (that we haven't seen) and determine //the first and last for (i = iStartIndex; i < MAX_DSN_ACTIONS; i++) { if (rgPossibleDSNActions[i] & *pdwActualDSNAction) { iLastMatch = i; if (MAX_DSN_ACTIONS == iFirstMatch) iFirstMatch = i; } } if (MAX_DSN_ACTIONS == iLastMatch) { //No matches... we are done *pdwActualDSNAction = 0; *pcIterationsLeft = 0; return; } //If this is possible after the above check... then I've screwed up _ASSERT(MAX_DSN_ACTIONS != iFirstMatch); *pdwActualDSNAction = rgPossibleDSNActions[iFirstMatch]; if ((iLastMatch == iFirstMatch) || ((rgPossibleDSNActions[iFirstMatch] == DSN_ACTION_FAILURE_ALL) && (rgPossibleDSNActions[iLastMatch] == DSN_ACTION_FAILURE))) { //This is our last time through *pcIterationsLeft = 0; } else { *pcIterationsLeft = MAX_DSN_ACTIONS-1-iFirstMatch; } } //---[ CDefaultDSNSink::HrWriteHumanReadableListOfRecips ]--------------------- // // // Description: // Writes a list of recipients to the human readable portion // Parameters: // IN pIMailMsgRecipients Recipients interface // IN prpfctxt Recipient filter context for this DSN // IN dwDSNActionsNeeded Type of DSN that we are generating // IN pdsnbuff DSN buffer to write DSN to // Returns: // S_OK on success // History: // 12/15/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrWriteHumanReadableListOfRecips( IN IMailMsgRecipients *pIMailMsgRecipients, IN RECIPIENT_FILTER_CONTEXT *prpfctxt, IN DWORD dwDSNActionsNeeded, IN CDSNBuffer *pdsnbuff) { HRESULT hr = S_OK; DWORD iCurrentRecip = 0; DWORD dwCurrentRecipFlags = 0; DWORD dwCurrentDSNAction = 0; CHAR szBuffer[PROP_BUFFER_SIZE]; CHAR szAddressType[PROP_BUFFER_SIZE]; hr = pIMailMsgRecipients->GetNextRecipient(prpfctxt, &iCurrentRecip); while (SUCCEEDED(hr)) { hr = pIMailMsgRecipients->GetDWORD(iCurrentRecip, IMMPID_RP_RECIPIENT_FLAGS, &dwCurrentRecipFlags); if (FAILED(hr)) goto Exit; //If the recipient matches our actions... print it if (fdwGetDSNAction(dwDSNActionsNeeded, &dwCurrentRecipFlags, &dwCurrentDSNAction)) { hr = HrGetRecipAddressAndType(pIMailMsgRecipients, iCurrentRecip, PROP_BUFFER_SIZE, szBuffer, sizeof(szAddressType), szAddressType); if (SUCCEEDED(hr)) { //write address value hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_INDENT, sizeof(DSN_INDENT)-sizeof(CHAR)); if (FAILED(hr)) goto Exit; hr = pdsnbuff->HrWriteBuffer((BYTE *) szBuffer, lstrlen(szBuffer)); if (FAILED(hr)) goto Exit; #ifdef NEVER //Print the recipient flags as well wsprintf(szBuffer, " (0x%08X)", dwCurrentRecipFlags); pdsnbuff->HrWriteBuffer((BYTE *) szBuffer, lstrlen(szBuffer)); #endif //NEVER hr = pdsnbuff->HrWriteBuffer((BYTE *) DSN_CRLF, sizeof(DSN_CRLF)-sizeof(CHAR)); if (FAILED(hr)) goto Exit; } else { //move along... these are not the error results you are interested in. hr = S_OK; } } hr = pIMailMsgRecipients->GetNextRecipient(prpfctxt, &iCurrentRecip); } Exit: return hr; } //---[ CDefaultDSNSink::HrGetRecipAddressAndType ]----------------------------- // // // Description: // Gets the recipient address and returns a pointer to the appropriate // string constant for the address type. // Parameters: // IN pIMailMsgRecipients Ptr To recipients interface // IN iRecip Index of the recipient of interest // IN cbAddressBuffer Size of buffer for address // IN OUT pbAddressBuffer Address buffer to dump address in // IN cbAddressType Size of buffer for address type // IN OUT pszAddressType Buffer for address type. // Returns: // S_OK on success // MAILMSG_E_PROPNOTFOUND if no address properties could be found // History: // 12/16/98 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrGetRecipAddressAndType( IN IMailMsgRecipients *pIMailMsgRecipients, IN DWORD iRecip, IN DWORD cbAddressBuffer, IN OUT LPSTR szAddressBuffer, IN DWORD cbAddressType, IN OUT LPSTR szAddressType) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrGetRecipAddressAndType"); HRESULT hr = S_OK; BOOL fFoundAddress = FALSE; DWORD i = 0; LPSTR szDelimiterLocation = NULL; CHAR szXDash[] = "x-"; CHAR chSave = '\0'; _ASSERT(szAddressType); _ASSERT(cbAddressType); _ASSERT(cbAddressBuffer); _ASSERT(szAddressBuffer); _ASSERT(pIMailMsgRecipients); szAddressType[0] = '\0'; szAddressBuffer[0] = '\0'; for (i = 0; i < NUM_DSN_ADDRESS_PROPERTIES; i ++) { hr = pIMailMsgRecipients->GetStringA(iRecip, g_rgdwRecipPropIDs[i], cbAddressBuffer, szAddressBuffer); if (SUCCEEDED(hr)) { fFoundAddress = TRUE; strncpy(szAddressType, g_rgszAddressTypes[i], cbAddressType); break; } } if (!fFoundAddress) { hr = MAILMSG_E_PROPNOTFOUND; ErrorTrace((LPARAM) this, "Unable to find recip %d address for message", iRecip); } else if (IMMPID_RP_ADDRESS_OTHER == g_rgdwRecipPropIDs[i]) { //Handle special case of IMMPID_RP_ADDRESS_OTHER... we should attempt to //parse out address from "type:address" format of IMMPID_RP_ADDRESS_OTHER //property szDelimiterLocation = strchr(szAddressBuffer, ':'); if (szDelimiterLocation && cbAddressType > sizeof(szXDash)) { chSave = *szDelimiterLocation; *szDelimiterLocation = '\0'; DebugTrace((LPARAM) this, "Found Address type of %s", szAddressBuffer); strncpy(szAddressType, szXDash, cbAddressType); strncat(szAddressType, szAddressBuffer, cbAddressType - (sizeof(szXDash)-sizeof(CHAR))); *szDelimiterLocation = chSave; } else { ErrorTrace((LPARAM) this, "Unable to find address type for address %s", szAddressBuffer); } } DebugTrace((LPARAM) this, "Found recipient address %s:%s for recip %d (propery %i:%x)", szAddressType, szAddressBuffer, iRecip, i, g_rgdwRecipPropIDs[i]); TraceFunctLeave(); return hr; } //---[ CDefaultDSNSink::HrMarkAllRecipFlags ]---------------------------------- // // // Description: // Marks all recipient according to the DSN action. Used when an NDR of // an NDR is found and we will not be generating a DSN, but need to mark // the recips so we can not generate 2 badmail events. // Parameters: // IN dwDSNAction The current DSN action // IN pIMailMsgProperties The mailmsg // IN prpfctxt Recipient filter context // Returns: // S_OK on success // History: // 1/11/99 - MikeSwa Created // //----------------------------------------------------------------------------- HRESULT CDefaultDSNSink::HrMarkAllRecipFlags( IN DWORD dwDSNAction, IN IMailMsgRecipients *pIMailMsgRecipients, IN RECIPIENT_FILTER_CONTEXT *prpfctxt) { TraceFunctEnterEx((LPARAM) this, "CDefaultDSNSink::HrMarkAllRecipFlags"); HRESULT hr = S_OK; DWORD iCurrentRecip = 0; DWORD dwCurrentRecipFlags = 0; DWORD dwCurrentDSNAction = 0; _ASSERT(pIMailMsgRecipients); hr = pIMailMsgRecipients->GetNextRecipient(prpfctxt, &iCurrentRecip); while (SUCCEEDED(hr)) { hr = pIMailMsgRecipients->GetDWORD(iCurrentRecip, IMMPID_RP_RECIPIENT_FLAGS, &dwCurrentRecipFlags); if (FAILED(hr)) goto Exit; if (fdwGetDSNAction(dwDSNAction, &dwCurrentRecipFlags, &dwCurrentDSNAction)) { hr = pIMailMsgRecipients->PutDWORD(iCurrentRecip, IMMPID_RP_RECIPIENT_FLAGS, dwCurrentRecipFlags); } hr = pIMailMsgRecipients->GetNextRecipient(prpfctxt, &iCurrentRecip); } Exit: if (HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) == hr) hr = S_OK; TraceFunctLeave(); return hr; }