XPLOGON.CPP

////////////////////////////////////////////////////////////////////////////// 
//
// File Name
// XPLOGON.CPP
//
// Description
// This file implements the IXPLogon interface with the methods specified
// in the MAPI SPI 1.0.
//
// Authors
// Irving De la Cruz
// Les Thaler
//
// Revision: 1.7
//
// Written for Microsoft Windows Developer Support
// Copyright (c) 1995-1996 Microsoft Corporation. All rights reserved.
//
//
#include "XPWDSR.H"

#define DO_INFO_TRACES
#ifdef DO_INFO_TRACES
#define InfoTrace(a) TraceInfoMessage(a)
#else
#define InfoTrace(a)
#endif // DO_INFO_TRACES

// Remark this line to turn verbose tracing OFF
MAPIUID guidABEntries = AB_UID_PROVIDER;
MAPIUID guidXPABEntries = XP_WINDS_AB_ENTRIES;
MAPIUID * puidWINDSEntries;
TCHAR gszProviderName[] = TEXT("WINDS Transport Service");
LPTSTR gszWINDSAddressType = WINDS_ADDRESS_TYPE;
LPTSTR * gpszXPAddressTypes;

void CALLBACK UploadTimerProc (HWND hOwnerWnd,
UINT Message,
UINT idEvent,
DWORD dwTime);

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::CXPLogon()
//
// Parameters
// hInstance Instance of the provider DLL
// pSupObj Pointer to IMAPISupport object used in
// CXPLogon methods
// pszHeadersFile Full file path where the remote headers are
// store in the local computer.
// fGetHeaders Weather or not to get the headers of the
// mailbox during a scheduled connection.
// pUserMailboxInfo Pointer to a MAILBOX_INFO structure with the
// information of the logged user as store in
// the WINDS server.
// dwMailboxID ID of the user in the WINDS system.
// pszRemoteServer Name of the remote WINDS server to which
// this transport communicates
// hUIMutex Handle to a WINDS mutex to allow single access
// to the UI configuration of this provider.
//
// Purpose
// Constructor of the object. Parameters are passed to initialize the
// data members with the appropiate values.
//
// Return Value
// None
//
CXPLogon::CXPLogon (HINSTANCE hInstance,
LPMAPISUP pSupObj,
LPTSTR pszHeadersFile,
BOOL fGetHeaders,
PMAILBOX_INFO pUserMailboxInfo,
DWORD dwMailboxID,
LPTSTR pszRemoteServer,
HANDLE hUIMutex)
{
InfoTrace ("CXPLogon: Constructor called");
m_cRef = 1;
m_pIdentityProps = NULL;
m_hInstance = hInstance;
m_pSupObj = pSupObj;
m_pStatusObj = NULL;
m_pIdentityProps = NULL;
ZeroMemory (&m_stDelivTime, sizeof(SYSTEMTIME));
m_uTimerID = 0;
m_hTimerWnd = NULL;
m_fABWDSInstalled = FALSE;
m_fGetHeaders = fGetHeaders;
m_hUIMutex = hUIMutex;
m_raAction = REMOTE_ACTION_IDLE;
m_UserInfo = *pUserMailboxInfo;
ZeroMemory (&m_UserEID, CB_PRIVATE_EID);
m_UserEID.uidGlobal = guidABEntries;
m_UserEID.bVersion = ENTRYID_VERSION;
m_UserEID.bObject = MAPI_MAILUSER;
m_UserEID.dwObjID = dwMailboxID;

m_pSupObj->AddRef();

lstrcpy (m_szHeaders, pszHeadersFile);
lstrcpy (m_szServer, pszRemoteServer);

wsprintf (m_szAddress, TEXT("%s\\%s"), m_szServer, m_UserInfo.szMailboxName);

// Initialize the messages download list object
m_List.SetLogon(this);
if (!m_List.Init())
{
throw CException (E_OUTOFMEMORY);
}
SetTransportState (WAITING);
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::~CXPLogon()
//
// Parameters
// None
//
// Purpose
// Destructor of CXPLogon. Releases memory allocated for internal
// properties during the life of this transport logon object.
//
// Return Value
// None
//
CXPLogon::~CXPLogon()
{
InfoTrace ("CXPLogon: Destructor called");
gpfnFreeBuffer (m_pIdentityProps);
CloseHandle (m_hUIMutex);

EmptyInboundQueue();

// Delete CMAPIStatus Object
/* if (m_pStatusObj)
{
m_pStatusObj->Release();
m_pStatusObj = NULL;
}
*/
// Release the IMAPISupport object
m_pSupObj->Release();
m_pSupObj = NULL;

// Just as a safety precaution, make sure the timer is off.
StopUploadTimer();

// Just in case the DLL is still hanging around
PrivUninitialize3DCtl (m_hInstance);
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::QueryInterface()
//
// Parameters
// { Refer to OLE Documentation on this method }
//
// Purpose
// Returns a pointer to a interface requested if the interface is
// supported and implemented by this object. If it is not supported, it
// returns NULL
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::QueryInterface (REFIID riid, LPVOID * ppvObj)
{
// OLE requires NULLing parameter
*ppvObj = NULL;
// If this is one of the two IID return an interface pointer to it
if (riid == IID_IXPLogon || riid == IID_IUnknown)
{
*ppvObj = (LPVOID)this;
// Increase usage count of this object
AddRef();
return S_OK;
}
// This object does not support the interface requested
return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////
// IXPLogon virtual member functions implementation
//

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::AddressType()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// Called by the MAPI Spooler when initializing this XP logon object to
// allow the transport to register the address it will handle
//
// Return Value
// S_OK always
//
STDMETHODIMP CXPLogon::AddressTypes (ULONG * pulFlags,
ULONG * pcAdrType,
LPTSTR ** pppAdrTypeArray,
ULONG * pcMAPIUID,
LPMAPIUID ** pppMAPIUIDArray)
{
InfoTrace ("CXPLogon::AddressTypes method called");
CheckParameters_IXPLogon_AddressTypes (this,
pulFlags,
pcAdrType,
pppAdrTypeArray,
pcMAPIUID,
pppMAPIUIDArray);

// This is how many address type strings we are returning in pppAdrTypeArray
*pcAdrType = 1;

// Copy address types back to the MAPI pointer
gpszXPAddressTypes = &gszWINDSAddressType;
*pppAdrTypeArray = gpszXPAddressTypes;
*pulFlags = fMapiUnicode;

// The UID for routing message has to be set here. If it is not used, set it NULL.
// In the case of WINDS transport registers support for the UID of
// the WINDS address book's entry ID
puidWINDSEntries = &guidABEntries;
*pcMAPIUID = 1;
*pppMAPIUIDArray = &puidWINDSEntries;
return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::RegisterOptions()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// This transport does not registers any per-recipient or per-message
// option processing, so we return 0 options. And NULL in the OPTIONDATA
// structure pointer.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::RegisterOptions (ULONG * pulFlags,
ULONG * pcOptions,
LPOPTIONDATA * ppOptions)
{
InfoTrace ("CXPLogon::RegisterOptions method called");
CheckParameters_IXPLogon_RegisterOptions (this,
pulFlags,
pcOptions,
ppOptions);
*pulFlags = 0;
*pcOptions = 0;
*ppOptions = NULL;
return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::TransportNotify()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// Update the status row registered by this transport with MAPI
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::TransportNotify (ULONG * pulFlags, LPVOID * ppvData)
{
InfoTrace ("CXPLogon::TransportNotify method called");
CheckParameters_IXPLogon_TransportNotify (this,
pulFlags,
ppvData);

ULONG ulOldStatus = GetTransportStatusCode();
// Set appropriate status flags and re-register status row
if (*pulFlags & NOTIFY_BEGIN_INBOUND)
{
InfoTrace ("CXPLogon::TransportNotify: Begin INBOUND logic");
AddStatusBits (STATUS_INBOUND_ENABLED);
}
if (*pulFlags & NOTIFY_END_INBOUND)
{
InfoTrace ("CXPLogon::TransportNotify: Terminate INBOUND logic");
RemoveStatusBits (STATUS_INBOUND_ENABLED);
}
if (*pulFlags & NOTIFY_BEGIN_OUTBOUND)
{
InfoTrace ("CXPLogon::TransportNotify: Begin OUTBOUND logic");
AddStatusBits (STATUS_OUTBOUND_ENABLED);
InitializeTimer();
StartUploadTimer();
}
if (*pulFlags & NOTIFY_END_OUTBOUND)
{
InfoTrace ("CXPLogon::TransportNotify: Terminate OUTBOUND logic");
RemoveStatusBits (STATUS_OUTBOUND_ENABLED);
StopUploadTimer();
}
if (*pulFlags & NOTIFY_BEGIN_OUTBOUND_FLUSH)
{
InfoTrace ("CXPLogon::TransportNotify: Begin OUTBOUND flush");
// If the spooler need to flush us for some reason, we put ourselves
// in FLUSH and tell the spooler to give us any deferred messages
// it has queued up for us.
AddStatusBits (UPLOADING_MESSAGES); // Add these bits to the status code
SetTransportState (READY);
m_pSupObj->SpoolerNotify (NOTIFY_SENTDEFERRED, NULL);
}
if (*pulFlags & NOTIFY_END_INBOUND_FLUSH)
{
InfoTrace ("CXPLogon::TransportNotify: Terminate INBOUND flush");
if (FALSE == m_List.AreTherePendingDownloads())
{
RemoveStatusBits (DOWNLOADING_MESSAGES); // Tell the spooler to take us off from inbound flush
AddStatusBits (STATUS_OFFLINE); // The transport is now OFF line
UpdateStatus (TRUE, TRUE);
}
}
if (*pulFlags & NOTIFY_END_OUTBOUND_FLUSH)
{
InfoTrace ("CXPLogon::TransportNotify: Terminate OUTBOUND flush");
if (GetTransportStatusCode() & UPLOADING_MESSAGES)
{
// When the spooler finished sending messages to our CXPLogon::SubmitMessage
// method, he will call us to let us know there is nothing else we need to
// flush, and we need to reset our status row.
RemoveStatusBits (UPLOADING_MESSAGES); // Tell the spooler to take us off from outbound flush
AddStatusBits (STATUS_OFFLINE); // The transport is now OFF line
if (PENDING_RETURN_CODE != GetTransportState())
{
SetTransportState (WAITING);
}
}
}

// We get called here if user deletes/modifies queued msg in Outbox.
// If the user modifies and resends it, we get called again at SubmitMessage.
// If the user closes the message viewer or just saves it, it's removed
// from the spooler queue and discarded.
if (*pulFlags & NOTIFY_ABORT_DEFERRED)
{
InfoTrace ("CXPLogon::TransportNotify: Abort deferred message");
}
if (*pulFlags & NOTIFY_CANCEL_MESSAGE)
{
InfoTrace ("CXPLogon::TransportNotify: Cancel message");
}
if (ulOldStatus != GetTransportStatusCode())
{
UpdateStatus();
}
if (*pulFlags & NOTIFY_BEGIN_INBOUND)
{
// If we have pending message from a previous session, notify the
// spooler that we are ready to give them to the store.
if (m_List.AreTherePendingDownloads())
{
// Put ourselves into flush mode and have the spooler call us until
// we are finished putting the downloaded messages into the default store.
AddStatusBits (DOWNLOADING_MESSAGES); // Add these bits to the status code
UpdateStatus();
}
}
return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::Idle()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// Stub method. We should not get called here, because we told
// the spooler not to call us here.
//
// Return Value
// S_OK always.
//
STDMETHODIMP CXPLogon::Idle (ULONG ulFlags)
{
InfoTrace ("CXPLogon::Idle method called");
CheckParameters_IXPLogon_Idle (this, ulFlags);
return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::TransportLogoff()
//
// Parameters
// ulFlags Priority with which the transport should log off
//
// Purpose
// This method is called by the spooler when the transport should do final
// arragements before it gets released.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::TransportLogoff (ULONG ulFlags)
{
InfoTrace ("CXPLogon::TransportLogoff method called");
CheckParameters_IXPLogon_TransportLogoff (this, ulFlags);
StopUploadTimer();

EmptyInboundQueue();

// We should attempt to remove the transport's status row from
// the system, but if we fail we won't fail the call.
HRESULT hResult = m_pSupObj->ModifyStatusRow (0, NULL, 0);
TraceResult ("CXPLogon::TransportLogoff: Failed to remove status row", hResult);

if (m_pStatusObj)
{
m_pStatusObj->Release();
m_pStatusObj = NULL;
}

return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::SubmitMessage()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// This method is called by the spooler when a client submits a
// message to a recipient whose address type this transport handles.
// The spooler calls this method twice for each deferred message.
// The first time (before the delivery time) when the message is
// submitted by the client, we simply return. The message is then queued
// by the spooler for later delivery. We keep track of when it's time
// to send deferred messages.
//
// The second time we're called, the state variable will be 'READY' and
// we go ahead and start the actual transmission. While we're in the
// body of this function, the implied state is 'SENDING'
//
// If the client logs out of this session, any pending messages get
// queued again the next time it logs in.
//
// In this transport we get a recipient table, we restrict the table for
// unmarked recipients. After the table is ready we invoke a helper
// method to do the actual transmission.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::SubmitMessage (ULONG ulFlags,
LPMESSAGE pMsgObj,
ULONG * pulMsgRef,
ULONG * pulReturnParm)
{
InfoTrace ("CXPLogon::SubmitMessage method called");
CheckParameters_IXPLogon_SubmitMessage (this,
ulFlags,
pMsgObj,
pulMsgRef,
pulReturnParm);

#ifdef _DEBUG
// Before we get called in SubmitMessage or StartMessage, the spooler should have called
// us in TransportNotify() to let us know which logic (IN, OUT, BOTH) of the
// transporting mechanism should be activated for this IXPLogon session.
if (!(GetTransportStatusCode() & STATUS_OUTBOUND_ENABLED))
{
TraceMessage ("CXPLogon::SubmitMessage: We can't send a message if the outbound is not enabled");
ASSERTMSG (FALSE, "We can't submit a message if we have not started OUTBOUND logic");
return MAPI_E_NOT_ME;
}
#endif // _DEBUG

// Lock the object against the timer thread re-entrancy. Timer events are handle
// in a separate thread. This section will get unlocked in the CPXLogon::EndMessage() call
if (WAITING == GetTransportState())
{
// Not scheduled upload time, return and let IXPLogon::EndMessage() defer the message
InfoTrace ("CXPLogon::SubmitMessage: Deferring message for scheduled submission");
return S_OK;
}
InfoTrace ("CXPLogon::SubmitMessage: Processing deferred message");

CheckSpoolerYield (TRUE);

// Notify the MAPI spooler the transport is going to flush any queued up messages and submit them.
// Transport to used shared resources, such as COM ports, should put them selves into flush mode
// when submitting a batch of messages. This gives the transport the advantage of locking shared
// resources across submitted messages and ensures that the spooler does not call other
// transports while another is flushing because it could have something locked.
// This transport does not uses any shared resources that we would need to lock, but for the
// purposes of demonstration, we put our selves into flush mode until the MAPI spooler
// tells us to step down.
if (!(GetTransportStatusCode() & UPLOADING_MESSAGES))
{
AddStatusBits (UPLOADING_MESSAGES);
UpdateStatus();
}

// Get the recipient table from the message
LPMAPITABLE pTable = NULL;
HRESULT hResult = pMsgObj->GetRecipientTable (fMapiUnicode, &pTable);
if (hResult)
{
TraceResult ("CXPLogon::SubmitMessage: Failed to get the recipient table", hResult);
goto ErrorExit;
}

// The spooler marks all the message recipients this transport has to
// handle with PR_RESPONSIBILITY set to FALSE
SPropValue spvRecipUnsent;
spvRecipUnsent.ulPropTag = PR_RESPONSIBILITY;
spvRecipUnsent.Value.b = FALSE;

SRestriction srRecipientUnhandled;
srRecipientUnhandled.rt = RES_PROPERTY;
srRecipientUnhandled.res.resProperty.relop = RELOP_EQ;
srRecipientUnhandled.res.resProperty.ulPropTag = PR_RESPONSIBILITY;
srRecipientUnhandled.res.resProperty.lpProp = &spvRecipUnsent;

hResult = pTable->Restrict (&srRecipientUnhandled, 0L);
if (hResult)
{
TraceResult ("CXPLogon::SubmitMessage: Failed to restrict the recipient table", hResult);
goto ErrorExit;
}

// Let the MAPI spooler do other things
CheckSpoolerYield();

hResult = HrAddColumns (pTable,
(LPSPropTagArray)&sptRecipTable,
gpfnAllocateBuffer,
gpfnFreeBuffer);
if (hResult)
{
TraceResult ("CXPLogon::SubmitMessage: Failed to expand the columns in the recipient table", hResult);
goto ErrorExit;
}

LPSRowSet pRecipRows;
hResult = HrQueryAllRows (pTable,
NULL,
NULL,
NULL,
0,
&pRecipRows);
if (!hResult)
{
// Let the MAPI spooler do other things
CheckSpoolerYield();
// Send to the recipients which we can reach, and generate NDR's for the ones we can't
hResult = SendMailMessage (pMsgObj, pRecipRows);
FreeProws (pRecipRows);
if (!hResult)
{
// Now we need to save changes on the message and close it.
// After this, the message object can't be used.
hResult = pMsgObj->SaveChanges (0);
TraceResult ("CXPLogon::SubmitMessage: Failed to save the message object", hResult);
}
}

ErrorExit:
// Release the table, we're finished with it
if (pTable)
{
pTable->Release();
}

// Release the spooler's message if needed to
if (pMsgObj)
{
pMsgObj->Release ();
}

// In case there is a warning or error floating around, don't let it escape to the spooler.
if (FAILED(hResult))
{
// We default to MAPI_E_NOT_ME so that the spooler would attempt handle
// the message to other transport (currently running in this profile)
// that handle the same address type as ours.
TraceMessage ("CXPLogon::SubmitMessage: An error occurred in the submission, returning MAPI_E_NOT_ME");
hResult = MAPI_E_NOT_ME;
}
else
{
hResult = S_OK;
}
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::EndMessage()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// This method is called by the spooler for each message we're to
// deliver. It's the mate to SubmitMessage. We're called here twice
// for each deferred message and once for non-deferred (realtime)
// messages.
//
// We first check the transport state, and if we're
// WAITING for the scheduled delivery time to arrive, we return
// END_DONT_RESEND in *pulFlags, which tells the spooler to queue this
// message for deferred delivery.
//
// If the state is SENDING, we're getting called here after
// a message has been dequeued and delivered. Return 0 in *pulFlags
// to tell the spooler the message has been delivered.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::EndMessage (ULONG ulMsgRef, ULONG * pulFlags)
{
InfoTrace ("CXPLogon::EndMessage method called");
CheckParameters_IXPLogon_EndMessage (this, ulMsgRef, pulFlags);

if (WAITING == GetTransportState())
{
// Tell spooler to queue msg for deferred delivery
*pulFlags = END_DONT_RESEND;
}
else
{
*pulFlags = 0;
}
return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::Poll()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// Stub method. We should not get called here, because we told
// the spooler not to call us here.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::Poll (ULONG * pulIncoming)
{
InfoTrace ("CXPLogon::Poll method called");
CheckParameters_IXPLogon_Poll (this, pulIncoming);
return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::StartMessage()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// This method gets called when an incoming message is pending to be
// processed.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::StartMessage (ULONG ulFlags,
LPMESSAGE pMsgObj,
ULONG * pulMsgRef)
{
InfoTrace ("CXPLogon::StartMessage method called");
CheckParameters_IXPLogon_StartMessage (this, ulFlags, pMsgObj, pulMsgRef);

// Initialize the pseudo-timer for the SpoolerYield() call
CheckSpoolerYield (TRUE);

HRESULT hResult = S_OK;
LPSTREAM pFileStream = NULL;
CCachedStream * pStream = NULL;
LPITNEF pTNEFObj = NULL;
SPropTagArray sptExcludedProps = { 0 };

PLIST_NODE pNode = m_List.GetDownloadNode();
if (!pNode)
{
RemoveStatusBits (DOWNLOADING_MESSAGES); // Remove these bits from the status code
UpdateStatus();
if (READY == GetTransportState())
{
SetTransportState (WAITING);
}
goto ErrorExit;
}
// Close the file so that we may open it in the call below
if (pNode->hFile)
{
CloseHandle (pNode->hFile);
// NULL the handle to avoid closing the file again during cleanup.
pNode->hFile = NULL;
}
// Open the stream where the message properties are.
hResult = OpenStreamOnFile (gpfnAllocateBuffer,
gpfnFreeBuffer,
STGM_READ,
pNode->szFileName,
NULL,
&pFileStream);
if (hResult)
{
TraceResult ("CXPLogon::StartMessage: Failed to open stream on the message file", hResult);
goto ErrorExit;
}
try
{
// Create a buffer IStream interface for the TNEF decoding
pStream = new CCachedStream (pFileStream, XPSOF_READ);
if (!pStream)
{
TraceMessage ("CXPLogon::StartMessage: Failed to allocate cached stream object");
hResult = E_OUTOFMEMORY;
}
}
catch (CException & Exception)
{
hResult = Exception.GetError();
}
pFileStream->Release();
pFileStream = NULL;
if (hResult)
{
goto ErrorExit;
}
hResult = OpenTnefStream (m_pSupObj,
pStream,
TNEF_FILE_NAME,
TNEF_DECODE,
pMsgObj,
0, // Not needed when decoding TNEF
&pTNEFObj);
if (hResult)
{
TraceResult ("CXPLogon::StartMessage: Failed to open TNEF stream object", hResult);
goto ErrorExit;
}

// Let the MAPI spooler do other things
CheckSpoolerYield();

// Get the properties for the message which are encoded in the TNEF
// message stream. The sptExcludedProps argument, is just a stub,
// we are not excluding any properties
LPSTnefProblemArray pProblems;
pProblems = NULL;
hResult = pTNEFObj->ExtractProps (TNEF_PROP_EXCLUDE, &sptExcludedProps, &pProblems);
if (pProblems)
{
gpfnFreeBuffer (pProblems);
pProblems = NULL;
}
if (hResult)
{
TraceResult ("CXPLogon::StartMessage: Failed to open TNEF stream", hResult);
goto ErrorExit;
}
if (MAPI_E_CORRUPT_DATA == SetIncomingProps (pMsgObj, pNode))
{
pNode = NULL; // To avoid deleting it below.
}

// Save all chages and new properties, back on the message
// Don't release the message object. The spooler is still using it.
// After this, the message object can't be used by this transport.
hResult = pMsgObj->SaveChanges (0);
TraceResult ("CXPLogon::StartMessage: Failed to save the message object", hResult);

// Let the MAPI spooler do other things
CheckSpoolerYield();

ErrorExit:
// Release used objects
if (pTNEFObj)
{
pTNEFObj->Release();
}
if (pStream)
{
pStream->Release();
}
if (pNode)
{
if (pNode->hFile)
{
CloseHandle (pNode->hFile);
}
// Delete the file ONLY if the we were successful putting the
// file data into the message. Otherwise leave the file in
// the directory, and it will be picked up later
if (S_OK == hResult)
{
DeleteFile (pNode->szFileName);
}
delete pNode;
}
m_List.UpdateProgress();
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::OpenStatusEntry()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// This method is called to get an IMAPIStatus object for this XPLOGON
// session.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::OpenStatusEntry (LPCIID pInterface,
ULONG ulFlags,
ULONG * pulObjType,

LPMAPISTATUS *  ppEntry) 
{
InfoTrace ("CXPLogon::OpenStatusEntry method called");
CheckParameters_IXPLogon_OpenStatusEntry (this,
pInterface,
ulFlags,
pulObjType,
ppEntry);
if (MAPI_MODIFY & ulFlags)
{
TraceMessage ("CXPLogon::OpenStatusEntry: We don't support WRITE access to the status object");
return E_ACCESSDENIED;
}

HRESULT hResult = S_OK;
// Now, if we already have created a status object on this logon context,
// we'll just use QueryInterface to get a new copy (AddRef() it) of the object
if (!m_pStatusObj)
{
// Get the profile section of the PROVIDER so that we may get some properties,
// assigned by MAPI to this provider, directly on the status object.
LPPROFSECT pProfileObj = NULL;
m_pSupObj->OpenProfileSection (NULL, 0, &pProfileObj);
// If we don't have an object, create it, and save a copy in the logon object
m_pStatusObj = new CMAPIStatus (this, pProfileObj);
if (!m_pStatusObj)
{
TraceMessage ("CXPLogon::OpenStatusEntry: Could not allocate new CMAPIStatus object");
hResult = E_OUTOFMEMORY;
}
// The constructor of CMAPIStatus AddRef()'ed this object
if (pProfileObj)
{
pProfileObj->Release();
}
}
if (!hResult)
{
// The transport will return *ppEntry == NULL or a pointer to the new memory allocated
m_pStatusObj->AddRef();
*ppEntry = m_pStatusObj;
*pulObjType = MAPI_STATUS;
}
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::ValidateState()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// This function gets caller by a client in order to validate the
// transport logon properties. This function open the profile with the
// most up-to-date properties and then compares them to what the transport
// has stored internally
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::ValidateState (ULONG ulUIParam, ULONG ulFlags)
{
InfoTrace ("CXPLogon::ValidateState method called");
CheckParameters_IXPLogon_ValidateState (this,
ulUIParam,
ulFlags);

// Try to open the transport profile section
LPPROFSECT pProfileObj;
HRESULT hResult = OpenServiceProfileSection (m_pSupObj, &pProfileObj, gpfnFreeBuffer);
if (hResult)
{
return hResult;
}

LPSPropValue pProps = NULL;
ULONG ulPropCount;
// Read the properties stored in the profile of this user
hResult = pProfileObj->GetProps ((LPSPropTagArray)&sptLogonProps,
fMapiUnicode,
&ulPropCount,
&pProps);
TraceResult ("CXPLogon::ValidateState: Failed to get profile properties", hResult);
if (!hResult)
{
// Now, compare what the transport thinks the information in the profile is
// to the real data. If they are different, tell the spooler the transport
// would like to be reloaded
if (lstrcmp (m_UserInfo.szMailboxName, pProps[MAILBOX_NAME].Value.LPSZ)||
lstrcmp (m_UserInfo.szPassword, pProps[PASSWORD].Value.LPSZ) ||
lstrcmp (m_UserInfo.szFullName, pProps[USER_NAME].Value.LPSZ) ||
lstrcmpi (m_szServer, pProps[SERVER_NAME].Value.LPSZ))
{
hResult = m_pSupObj->SpoolerNotify (NOTIFY_CONFIG_CHANGE, NULL);
TraceResult ("CXPLogon::ValidateState: Failed to notify the spooler", hResult);
}
}
pProfileObj->Release();
gpfnFreeBuffer (pProps);
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::FlushQueues()
//
// Parameters
// { Refer to MAPI Documentation on this method }
//
// Purpose
// Called by the MAPI spooler when, upon request of the client or
// ourselves, we need to flush the inbound or outbound queue.
// Here we make connections to the server to download messages, refresh
// the remote message headers, and request the spooler to send us any
// deferred messages.
// Transport connecting only in FlushQueues() allow the MAPI spooler to
// better manage contention of multiple transport accessing common
// communication resources (such as COM ports) and let the spooler give us
// messages to process when is best for the overall subsystem.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::FlushQueues (ULONG ulUIParam,
ULONG cbTargetTransport,
LPENTRYID pTargetTransport,
ULONG ulFlags)
{
InfoTrace ("CXPLogon::FlushQueues method called");
CheckParameters_IXPLogon_FlushQueues (this,
ulUIParam,
cbTargetTransport,
pTargetTransport,
ulFlags);

HRESULT hResult = S_OK;
// Update status flags and then update update the transport status rows
if (ulFlags & FLUSH_UPLOAD)
{
// If the remote server is offline, the transport is not going to send the deferred messages
if (TRUE == IsWINDSServerAvailable (m_szServer))
{
// If we are already in delivery mode submitted messages will not get deferred,
// so don't bother the spooler asking for deferred messages.
if (READY != GetTransportState())
{
// Guard against re-entrancy from the timer call back which happens on a separate thread
if (PROCESSING_TIMER_EVENT != GetTransportState())
{
SetTransportState (READY);
}
// We pass NULL for the entry ID so that the spooler would resend ALL the
// deferred messages. Transport may pass individual entry IDs for specific messages.
hResult = m_pSupObj->SpoolerNotify (NOTIFY_SENTDEFERRED, NULL);
}
}
}
if (ulFlags & FLUSH_DOWNLOAD)
{
if (HEADERS_AND_DOWNLOAD == GetTransportState())
{
m_hRemoteActionErr = ProcessHeaders();
if (!m_hRemoteActionErr && !m_fCancelPending)
{
m_pSupObj->SpoolerYield (0);
if (m_List.AreTherePendingDownloads())
{
// Put ourselves into flush mode and have the spooler call us until
// we are finished putting the downloaded messages into the default store.
AddStatusBits (DOWNLOADING_MESSAGES); // Add these bits to the status code
}
if (!m_fCancelPending)
{
// Upload any deferred messages the spooler has for our transport
if (IsWINDSServerAvailable (m_szServer))
{
m_pSupObj->SpoolerNotify (NOTIFY_SENTDEFERRED, NULL);
}
if (!m_fCancelPending)
{
m_hRemoteActionErr = DownloadMessageHeaders();
if (!m_hRemoteActionErr && !(GetTransportStatusCode() & DOWNLOADING_MESSAGES))
{
RemoveStatusBits (STATUS_INBOUND_FLUSH);
UpdateStatus (TRUE, TRUE);
m_pSupObj->SpoolerYield (0);
}
}
}
}
SetTransportState (PENDING_RETURN_CODE);
AddStatusBits (STATUS_OFFLINE);
if (m_hRemoteActionErr)
{
RemoveStatusBits (STATUS_INBOUND_FLUSH);
UpdateStatus (TRUE, TRUE);
}
else
{
if (m_fCancelPending)
{
UpdateStatus (TRUE, TRUE);
}
else
{
UpdateStatus();
}
}
}
else
{
if (PROCESSING_TIMER_EVENT == GetTransportState())
{
if (m_fGetHeaders)
{
RemoveStatusBits (STATUS_OFFLINE);
UpdateStatus();
DownloadMessageHeaders();
AddStatusBits (STATUS_OFFLINE);
UpdateStatus();
}
}
else
{
if (m_List.AreTherePendingDownloads())
{
// Put ourselves into flush mode and have the spooler call us until
// we are finished putting the downloaded messages into the default store.
AddStatusBits (DOWNLOADING_MESSAGES); // Add these bits to the status code
UpdateStatus();
}
}
}
}
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::ProcessHeaders()
//
// Parameters
// None.
//
// Purpose
// This method is called from the status object's ValidateState when
// it's passed PROCESS_XP_HEADER_CACHE. It starts the download processing.
// First we get the header folder contents table and restrict it to rows
// where PR_MSG_STATUS is >= MSGSTATUS_REMOTE_DOWNLOAD. These are rows
// for messages that have the MSGSTATUS_REMOTE_DOWNLOAD, or
// MSGSTATUS_REMOTE_DELETE, or both, bits set.
//
// We then connect to the server to obtain the download named pipe and
// start the download.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::ProcessHeaders()
{
// If we don't have a folder in the status object or the folder doesn't
// have an initialized contents table, there is nothing to do.
if (!m_pStatusObj->m_pHeaderFolder || !m_pStatusObj->m_pHeaderFolder->m_pTableData)
{
return S_OK;
}

// Get headers folder contents table, some are marked for download
LPMAPITABLE pTable;
HRESULT hResult = m_pStatusObj->m_pHeaderFolder->m_pTableData->HrGetView (NULL, NULL, 0, &pTable);
if (hResult)
{
TraceResult ("CXPLogon::ProcessHeaders: Failed to get a view on the contents table", hResult);
return hResult;
}

// we only want rows marked for download, form restriction
SPropValue spvFilter = { 0 };
spvFilter.ulPropTag = PR_MSG_STATUS;
spvFilter.Value.l = MSGSTATUS_REMOTE_DOWNLOAD;

// This restriction relies in the current values of the flags in PR_MSG_STATUS
SRestriction srStatus = { 0 };
srStatus.rt = RES_PROPERTY;
srStatus.res.resProperty.relop = RELOP_GE;
srStatus.res.resProperty.ulPropTag = PR_MSG_STATUS;
srStatus.res.resProperty.lpProp = &spvFilter;

hResult = pTable->Restrict (&srStatus, 0);
if (hResult)
{
// This is a non-fatal error (i.e. the implementation does not support restrictions)
TraceResult ("CXPLogon::ProcessHeaders: Failed to restrict the table", hResult);
}
ULONG ulRowCount;
// Providers and client should not, when ever possible, use IMAPITable::GetRowCount()
// because depending on the implementation of the table, the returned count by not
// be exact.
// We use it here for convenience knowing the fact the MAPI's ITableData implementation
// returns and EXACT count of the rows in the memory table.
hResult = pTable->GetRowCount (0, &ulRowCount);
if (hResult)
{
goto ErrorExit;
}
// Nothing to do.
if (0 == ulRowCount)
{
goto ErrorExit;
}
else
{
// Make sure our download directory is around.
TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH];
GetTempPath (_MAX_PATH, szTmpDir);
lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, m_UserInfo.szMailboxName);
CreateDirectory (szDownloadDir, NULL);
}

// Initialize the progress property in the status row of this transport.
UpdateProgress (0, REMOTE_ACTION_DOWNLOADING_MSGS);

// Make a call to the remote server to open the connection
HANDLE hPipe;
hResult = OpenRemoteServerDownLoadPipe (m_szServer, m_UserInfo.szMailboxName, &hPipe);
if (hResult)
{
goto ErrorExit;
}
// Do the actual download transmission now.
hResult = m_List.DownLoadMsgs (pTable, ulRowCount, hPipe);
CloseHandle (hPipe);

TerminateRemoteConnections();

ErrorExit:
TraceResult ("CXPLogon::ProcessHeaders", hResult);
pTable->Release();
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::UpdateProgress()
//
// Parameters
// lPercentComplete Percent completion of download
// raFlag Flag that indicate to what remote action the
// update is tied to, so that we may properly
// update PR_REMOTE_PROGRESS_TEXT.
//
// Purpose
// Sets the status row property PR_REMOTE_PROGRESS with a number from
// 0-100. In example, if 10 messages are queued and 5 have been
// downloaded, lPercentComplete will be 50, regardless of individual
// messages' sizes.
//
// Return Value
// None.
//
void WINAPI CXPLogon::UpdateProgress (long lPercentComplete,
REMOTE_ACTION raFlag)
{
SPropValue spProgress[2] = { 0 };
ULONG cProps = 1;
TCHAR szRemoteProgress[64] = { 0 };
spProgress[0].ulPropTag = PR_REMOTE_PROGRESS;
spProgress[0].Value.l = lPercentComplete;
if (raFlag != m_raAction)
{
spProgress[1].ulPropTag = PR_REMOTE_PROGRESS_TEXT;
spProgress[1].Value.LPSZ = szRemoteProgress;
DWORD idString;
m_raAction = raFlag;
switch (raFlag)
{
case REMOTE_ACTION_DOWNLOADING_MSGS :
idString = IDS_REMOTE_DOWNLOADING;
break;
case REMOTE_ACTION_PROCESSING_MSGS :
idString = IDS_REMOTE_PROCESSING_MSGS;
break;
case REMOTE_ACTION_HEADER_REFRESH :
idString = IDS_REMOTE_REFRESHING_HEADERS;
break;
case REMOTE_ACTION_IDLE :
idString = 0;
break;
}
if (idString)
{
LoadString (m_hInstance, idString, szRemoteProgress, 64);
}
cProps++;
}

HRESULT hResult = m_pSupObj->ModifyStatusRow (cProps, spProgress, STATUSROW_UPDATE);
TraceResult ("CXPLogon::UpdateProgress: Failed to modify the status row", hResult);
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::LoadStatusString()
//
// Parameters
// pString Pointer to a string which will hold the status string
// uStringSize Maximum number of characters allowed in the string
//
// Purpose
// Loads a string from the transport's stringtable. This method is called
// by the CXPLogon::UpdateStatus method when updating a status row. This
// method loads the string based on the status bits of the transport
// status code
//
// Return Value
// TRUE If the string was found in the string table.
// FALSE The string was not found. The String indicated by
// pString is set to hold 0 characters
//
BOOL WINAPI CXPLogon::LoadStatusString (LPTSTR pString, UINT uStringSize)
{
UINT uStringID;
DWORD dwStatusCode = GetTransportStatusCode();
if (dwStatusCode & (DOWNLOADING_MESSAGES | UPLOADING_MESSAGES))
{
uStringID = IDS_XP_STATUS_TIMER_EVENT;
}
else
{
if (dwStatusCode & DOWNLOADING_MESSAGES)
{
if (dwStatusCode & STATUS_OFFLINE)
{
uStringID = IDS_XP_STATUS_FLUSHING_INBOUND;
}
else
{
uStringID = IDS_XP_STATUS_INBOUND_AND_HEADERS;
}
}
else
{
if (dwStatusCode & UPLOADING_MESSAGES)
{
uStringID = IDS_XP_STATUS_FLUSHING_OUTBOUND;
}
else
{
if (dwStatusCode & STATUS_INBOUND_ENABLED &&
dwStatusCode & STATUS_OUTBOUND_ENABLED)
{
uStringID = IDS_XP_STATUS_READY;
}
else
{
uStringID = IDS_XP_STATUS_AVAILABLE;
}
}
}
}
if (LoadString (m_hInstance, uStringID, pString, uStringSize))
{
return TRUE;
}
pString[0] = '\0';
return FALSE;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::MakeSearchKey()
//
// Parameters
// pParentMemBlock Pointer the a block of memory allocated with the
// MAPI Mem. alloc. functions.
// pszAddress String with the actual mail address
// pcbSearchKey Pointer to a ULONG which returns the size
// (in bytes) of the newly created search key
// ppSearchKey Pointer to an array ob BYTES
//
// Purpose
// Returns a MAPI search key of the form TYPE:ADDRESS in upper case.
// The string is allocated with the MAPI Mem Alloc functions and linked
// to the block of memory indicated by pParentMemBlock
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::MakeSearchKey (LPVOID pParentMemBlock,
LPTSTR pszAddress,
ULONG * pcbSearchKey,
LPBYTE * ppSearchKey)
{
LPTSTR pszSearchKey;
// The 2 is for the COLON and the NULL terminator
ULONG cbStrSize = sizeof (TCHAR) * (2 + lstrlen (WINDS_ADDRESS_TYPE) + lstrlen (pszAddress));
HRESULT hResult = gpfnAllocateMore (cbStrSize, pParentMemBlock, (LPVOID *)&pszSearchKey);
TraceResult ("CXPLogon::MakeSearchKey: Failed to allocate search key string", hResult);
if (!hResult)
{
// We need to convert the address to upper case
wsprintf (pszSearchKey, TEXT("%s:%s"), WINDS_ADDRESS_TYPE, pszAddress);
CharUpper (pszSearchKey);
*pcbSearchKey = cbStrSize;
*ppSearchKey = (LPBYTE)pszSearchKey;
}
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::UpdateStatus()
//
// Parameters
// fAddValidate
// fValidateOkState
//
// Purpose
// Updates the transport status row of this transport in the MAPI Mail
// subsystem. Updates the flags according the internal state flags
// maintained in status code of the transport and loads a readable status
// string to reset the status row. The caller of this method should update
// the status code member variable prior to calling UpdateStatus()
//
// Return Value
// None
//
void WINAPI CXPLogon::UpdateStatus (BOOL fAddValidate, BOOL fValidateOkState)
{
ULONG cProps = 1;
SPropValue rgProps[3] = { 0 };
// Store the new Transport Provider Status Code
rgProps[0].ulPropTag = PR_STATUS_CODE;
rgProps[0].Value.l = GetTransportStatusCode();

TCHAR szStatus[64];
if (LoadStatusString (szStatus, sizeof(szStatus) - 1 ))
{
rgProps[1].ulPropTag = PR_STATUS_STRING;
rgProps[1].Value.LPSZ = szStatus;
cProps++;
}
if (fAddValidate)
{
ULONG index;
if (2 == cProps)
{
index = 2;
}
else
{
index = 1;
}
rgProps[index].ulPropTag = PR_REMOTE_VALIDATE_OK;
rgProps[index].Value.b = fValidateOkState;
cProps++;
}
// OK. Notify the messaging subsystem of state changes in this transport.
HRESULT hResult = m_pSupObj->ModifyStatusRow (cProps, rgProps, STATUSROW_UPDATE);
TraceResult ("CXPLogon::UpdateStatus: ModifyStatusRow failed", hResult);
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::InitializeTransportStatusFlags()
//
// Parameters
// ulFlags Flags passed in to initialize the transport status flags.
//
// Purpose
// Initialize the transport status flags with the flags passed in by the
// MAPI spooler. This method gets called when initializing a CXPLogon
// object in the CXPProvider::TransportLogon method.
//
// Return Value
// None
//
void WINAPI CXPLogon::InitializeTransportStatusFlags (ULONG ulFlags)
{
AddStatusBits (STATUS_AVAILABLE | STATUS_OFFLINE | STATUS_REMOTE_ACCESS);
if (!(ulFlags & LOGON_NO_INBOUND))
{
AddStatusBits (STATUS_INBOUND_ENABLED);
}
if (!(ulFlags & LOGON_NO_OUTBOUND))
{
AddStatusBits (STATUS_OUTBOUND_ENABLED);
}
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::SetIdentityProps()
//
// Parameters
// None
//
// Purpose
// Initializes the transport ID array with the properties found in the
// profile for the user.
// If the ABWDS address book is installed, we open an entry in it to
// supply the identity properties.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::SetIdentityProps()
{
// Allocate the property array for transport. This memory block is freed
// in the destructor of the CXPLogon object
HRESULT hResult = gpfnAllocateBuffer (sizeof(SPropValue)*NUM_IDENTITY_PROPS, (LPVOID *)&m_pIdentityProps);
if (hResult)
{
TraceResult ("CXPLogon::SetIdentityProps: Failed to allocate property array", hResult);
return hResult;
}
LPMAILUSER pUser;
ULONG ulObjType;
TCHAR szSearchKey[128];
LPTSTR pStr;
// Try to open out entry ID in the ABWDS address book (see if it is around)
// If the call fails, we must create a one-off for our identity.
hResult = m_pSupObj->OpenEntry (CB_PRIVATE_EID,
(LPENTRYID)&m_UserEID,
NULL,
0,
&ulObjType,
(LPUNKNOWN *)&pUser);
if (S_OK == hResult)
{
ASSERTMSG (MAPI_MAILUSER == ulObjType, "What kind object is this?");

// Looks like ABWDS is around, mark the internal flag.
// ABWDS is the native WINDS address book service provider. If present,
// instead of creating a one-off entry for the transport identity, we
// will simply open an entry in the WINDS address book and get the
// necessary properties out from it.
m_fABWDSInstalled = TRUE;

m_pIdentityProps[XPID_NAME].ulPropTag = PR_SENDER_NAME;
m_pIdentityProps[XPID_NAME].Value.LPSZ = m_UserInfo.szFullName;
m_pIdentityProps[XPID_EID].ulPropTag = PR_SENDER_ENTRYID;
m_pIdentityProps[XPID_EID].Value.bin.cb = CB_PRIVATE_EID;
m_pIdentityProps[XPID_EID].Value.bin.lpb = (LPBYTE)&m_UserEID;
wsprintf (szSearchKey,
TEXT("%s:%s\\%s"),
WINDS_ADDRESS_TYPE,
m_szServer, // this is already in the format "\\<servername>"
m_UserInfo.szMailboxName);
CharUpper (szSearchKey);
hResult = gpfnAllocateMore (Cbtszsize(szSearchKey), m_pIdentityProps, (LPVOID *)&pStr);
if (hResult)
{
TraceResult ("CXPLogon::SetIdentityProps: Failed to allocate string for native search key", hResult);
m_pIdentityProps[XPID_SEARCH_KEY].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_SEARCH_KEY));
}
else
{
lstrcpy (pStr, szSearchKey);
m_pIdentityProps[XPID_SEARCH_KEY].ulPropTag = PR_SENDER_SEARCH_KEY;
m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.cb = Cbtszsize(pStr);
m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.lpb = (LPBYTE)pStr;
}
pUser->Release();
return hResult;
}

///////////////////////////////////////////////////////////////////////////
// Set the PR_SENDER_ENTRYID property. Create an entry for the user and
// set it in the property array
m_pIdentityProps[XPID_EID].ulPropTag = PR_SENDER_ENTRYID;
LPBYTE lpeid = NULL;
ULONG cbeid = 0;
hResult = m_pSupObj->CreateOneOff (m_UserInfo.szFullName,
WINDS_ADDRESS_TYPE,
m_szAddress,
fMapiUnicode,
&cbeid,
(LPENTRYID *)&lpeid);
if (hResult)
{
TraceResult ("CXPLogon::SetIdentityProps: Failed to create a one off identity", hResult);
m_pIdentityProps[XPID_EID].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_ENTRYID));
return hResult;
}

hResult = gpfnAllocateMore(cbeid, m_pIdentityProps,
(LPVOID *)&m_pIdentityProps[XPID_EID].Value.bin.lpb);
if(hResult)
{
TraceResult ("CXPLogon::SetIdentityProps: Failed to allocate memory", hResult);
m_pIdentityProps[XPID_EID].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_ENTRYID));
gpfnFreeBuffer(lpeid);
lpeid = NULL;
return hResult;
}

m_pIdentityProps[XPID_EID].Value.bin.cb = cbeid;
CopyMemory(m_pIdentityProps[XPID_EID].Value.bin.lpb, lpeid, cbeid);

gpfnFreeBuffer(lpeid);
lpeid = NULL;

//////////////////////////////////////////////////////////////////////////
// Set the PR_SENDER_NAME property
m_pIdentityProps[XPID_NAME].ulPropTag = PR_SENDER_NAME;
LPTSTR pStr1, pStr2 = m_UserInfo.szFullName;
hResult = gpfnAllocateMore (Cbtszsize(pStr2), m_pIdentityProps, (LPVOID *)&pStr1);
if (hResult)
{
TraceResult ("CXPLogon::SetIdentityProps: Memory allocation failed for display name string", hResult);
m_pIdentityProps[XPID_NAME].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_NAME));
return hResult;
}
lstrcpy (pStr1, pStr2);
m_pIdentityProps[XPID_NAME].Value.LPSZ = pStr1;

//////////////////////////////////////////////////////////////////////////
// Set the PR_SENDER_SEARCH_KEY property
m_pIdentityProps[XPID_SEARCH_KEY].ulPropTag = PR_SENDER_SEARCH_KEY;
// First, create the search key
hResult = MakeSearchKey (m_pIdentityProps,
m_szAddress,
&m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.cb,
&m_pIdentityProps[XPID_SEARCH_KEY].Value.bin.lpb);
if (hResult)
{
m_pIdentityProps[XPID_SEARCH_KEY].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_SENDER_SEARCH_KEY));
}
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::SetSessionFlags()
//
// Parameters
// [IN, OUT] pulFlags Flags returned to caller
//
// Purpose
// To set the flags we will return to the spooler during the CXPLogon
// initialization in the CXPProvider::TransportLogon method. The ulFlags
// parameter is filled as a return value to the caller
//
// Return Value
// None
//
void WINAPI CXPLogon::SetSessionFlags (ULONG * pulFlags)
{
if (*pulFlags & LOGON_NO_CONNECT)
{
// Nothing is set
*pulFlags = 0;
}
else
{
// We DON'T want the MAPI spooler to call our IXPLogon::Idle method (LOGON_SP_IDLE not added)
// We DON'T want the MAPI spooler to call our IXPLogon::Poll method (LOGON_SP_POLL not added)
// Also the names have to be fully resolved (LOGON_SP_RESOLVE)
*pulFlags = LOGON_SP_RESOLVE;
}
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::SendMailMessage()
//
// Parameters
// pMsgObj Pointer to an IMessage object passed to us by the
// MAPI spooler.

//      pRecipRows  Row set with ALL the rows from the recipient table which 
// have the properties of the recipients we are sending the
// message to.
//
// Purpose
// This method gets called by SubmitMessage() to do the delivery of the
// message to the list of recipients. Each recipient will have the
// PR_RESPONSIBILITY property set indicating weather or not message
// reached the recipient.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::SendMailMessage (LPMESSAGE pMsgObj, LPSRowSet pRecipRows)
{
LPSPropValue pMsgProps = NULL;
LPADRLIST pAdrList = NULL, pAdrListFailed = NULL;
LPSTnefProblemArray pProblems = NULL;
CCachedStream * pStream = NULL;
LPSTREAM pFileStream;
HANDLE hMsgFile = NULL;
HRESULT hOpenConnect, hUploadError;
BOOL fErrorInServer, fSentSuccessfully;
ULONG ulRow, ulCount1 = 0 , ulCount2 = 0;
LPTSTR pszServer, pszMailbox;
LPSPropValue pProps;
TCHAR szHeaderText[1024], szTmpFile[_MAX_PATH], szConnectInfo[MAX_STRING_SIZE+1] = { 0 };
if (!GetMsgTempFileName (szTmpFile)) // Not the Win32 API, but an internal implementation
{
return E_FAIL;
}

// Create a stream where all message information will be saved.
HRESULT hResult = OpenStreamOnFile (gpfnAllocateBuffer,
gpfnFreeBuffer,
STGM_CREATE | STGM_READWRITE,
szTmpFile,
NULL,
&pFileStream);
if (hResult)
{
TraceResult ("CXPLogon::SendMailMessage: Failed to create stream object", hResult);
return hResult;
}
try
{
pStream = new CCachedStream (pFileStream, XPSOF_READWRITE);
if (!pStream)
{
TraceMessage ("CXPLogon::SendMailMessage: Failed to allocate cached stream object");
hResult = E_OUTOFMEMORY;
}
}
catch (CException & Exception)
{
hResult = Exception.GetError();
}
pFileStream->Release();
pFileStream = NULL;
if (hResult)
{
return hResult;
}

// The wKey is a key used to identify the TNEF property stream. Transports
// should generate a pseudo-random number for this field. Here we get
// a number based upon the system's tic count
// Note that this number cannot be zero of the OpenTnefStream call will be fail.
WORD wKey = LOWORD (GetTickCount());
if (!wKey)
{
ASSERTMSG (FALSE, "I'll be darn! It's zero!!!");
wKey = LOWORD (GetTickCount()) + 1;
ASSERTMSG (0 != wKey, "No way! What is going on!?!?");
}
LPITNEF pTNEFObj = NULL;
hResult = OpenTnefStream (m_pSupObj,
pStream,
TNEF_FILE_NAME,
TNEF_ENCODE | TNEF_PURE,
pMsgObj,
wKey,
&pTNEFObj);
if (hResult)
{
TraceResult ("CXPLogon::SendMailMessage: Failed to create TNEF object", hResult);
goto ErrorExit;
}

// Let the MAPI spooler do other things
CheckSpoolerYield();

// Get the current time. We need to set some properties that require the time
SYSTEMTIME st;
FILETIME ft;
GetSystemTime (&st);
SystemTimeToFileTime (&st, &ft);

SetOutgoingProps (pMsgObj, ft);

// Check what properties there are and exclude the Non-Transmittables ones
LPSPropTagArray pTags;
hResult = pMsgObj->GetPropList (fMapiUnicode, &pTags);
if (hResult)
{
TraceResult ("CXPLogon::SendMailMessage: Failed to get the message property tags", hResult);
goto ErrorExit;
}

// Let the MAPI spooler do other things
CheckSpoolerYield();

// In this sample transport we opted to let TNEF encapsulate
// the table of recipients of this message. This has a side effect: The
// addresses of the recipients get munged into the TNEF stream. So, for
// example, if the TNEF file goes through a foreign mail system and it
// needs to translate the address of the recipients, it will not be
// able to. A tranport using TNEF which trasmits messages to a foreign
// mail system, must do custom processing of the recipients and their
// address so the receiving side will understands them.
// This sample code must be modified if you want it as the base code
// for a gateway transport.
// Here we also let TNEF encode all the attachments we want to send.
ULONG cValues, i;
cValues = 0;
for (i=0; i<pTags->cValues; i++)
{
// Use the FIsTransmittable macro in MAPI to determine if a
// property is transmittable or not.
if (!FIsTransmittable(pTags->aulPropTag[i]) &&
PR_MESSAGE_RECIPIENTS != pTags->aulPropTag[i] &&
PR_MESSAGE_ATTACHMENTS != pTags->aulPropTag[i])
{
pTags->aulPropTag[cValues++] = pTags->aulPropTag[i];
}
}
pTags->cValues = cValues;

// Encode the properties now
hResult = pTNEFObj->AddProps (TNEF_PROP_EXCLUDE, 0, NULL, pTags);
gpfnFreeBuffer (pTags);
if (FAILED(hResult)) // There could be warnings
{
TraceResult ("CXPLogon::SendMailMessage: Failed to Add the TNEF properties to the message stream", hResult);
goto ErrorExit;
}

hResult = pTNEFObj->Finish (0, &wKey, &pProblems);
if (FAILED(hResult)) // There could be warnings
{
TraceResult ("CXPLogon::SendMailMessage: Failed to save the TNEF props", hResult);
goto ErrorExit;
}
if (pProblems)
{
TraceMessage ("CXPLogon::SendMailMessage: Some problem(s) occurred while finishing the TNEF encoding");
gpfnFreeBuffer (pProblems);
pProblems = NULL;
// Don't have to fail submission
}

// Release used objects and NULL the object pointers to avoid re-releasing them during cleanup
if (NULL != pTNEFObj)
{
pTNEFObj->Release();
pTNEFObj = NULL;
}
if (NULL != pStream)
{
pStream->Release();
pStream = NULL;
}

hMsgFile = CreateFile (szTmpFile,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_TEMPORARY |
FILE_FLAG_DELETE_ON_CLOSE |
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
if (INVALID_HANDLE_VALUE == hMsgFile)
{
TraceResult ("CXPLogon::SendMailMessage: Failed to open local msg file", GetLastError());
hResult = E_FAIL;
goto ErrorExit;
}

// Get some properties in the message need to send the message
// and delivery report information
hResult = pMsgObj->GetProps ((LPSPropTagArray)&sptPropsForHeader,
fMapiUnicode,
&cValues,
&pMsgProps);
if (FAILED(hResult))
{
TraceResult ("CXPLogon::SendMailMessage: Failed to get message props", hResult);
goto ErrorExit;
}

// We need to check if the sender requeste a delivery report or not.
BOOL fNeedDeliveryReport;
if (PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED == pMsgProps[MSG_DR_REPORT].ulPropTag &&
pMsgProps[MSG_DR_REPORT].Value.b)
{
fNeedDeliveryReport = TRUE;
}
else
{
fNeedDeliveryReport = FALSE;
}

// Create the header that we transmit to the server. The remote host will
// store this information and make it available to us when then recipients
// of the message ask for a headers update on their remote mailboxes.
CreateMsgHeaderTextLine (pMsgProps, szHeaderText, ft);

// Let the MAPI spooler do other things
CheckSpoolerYield();

hOpenConnect = OpenServerUploadPipe (m_szServer,
m_UserInfo.szMailboxName,
hMsgFile,
szConnectInfo,
&fErrorInServer);
for (ulRow=0; ulRow<pRecipRows->cRows; ulRow++)
{
pProps = pRecipRows->aRow[ulRow].lpProps;

// Assume the worst. If hOpenConnect is not S_OK, the fSentSuccessfully
// must be FALSE to generate NDR for each recipient.
fSentSuccessfully = FALSE;
if (S_OK == hOpenConnect)
{
if (IsValidAddress (pProps[RECIP_EMAIL_ADR].Value.LPSZ, &pszServer, &pszMailbox))
{
// pszServer should be the same as m_szServer
hUploadError = SendMsgToAccount (pszServer,
pszMailbox,
szHeaderText,
szConnectInfo,
&fErrorInServer);
if (!hUploadError)
{
// If we got here, we assume the message has been received in the server
fSentSuccessfully = TRUE;
}
}
else
{
hUploadError = MAKE_HRESULT(1, FACILITY_WIN32, ERROR_INVALID_ADDRESS);
}
}
else
{
hUploadError = hOpenConnect;
}

// Let the MAPI spooler do other things
CheckSpoolerYield();

// Set the PR_RESPONSIBILITY flag to indicated we have handled this
// recipient (row). If the flag is not modified, MAPI will pass this
// message to the next transport in the profile that knows how to
// handle the address types this transport knows.
// In this case WE want to be the last to handle the message. If
// we fail the submission, we should tell MAPI to generate an NDR.
pProps[RECIP_RESPONSIBILITY].ulPropTag = PR_RESPONSIBILITY;
pProps[RECIP_RESPONSIBILITY].Value.b = TRUE;

// Set the report time for DR's and NDR's
pProps[RECIP_REPORT_TIME].ulPropTag = PR_REPORT_TIME;
pProps[RECIP_REPORT_TIME].Value.ft = ft;

if (!fSentSuccessfully)
{
// Make the spooler generate an NDR instead of DR
pProps[RECIP_DELIVER_TIME].ulPropTag = PR_NULL;

// The Spooler will generate an NDR report and will fill in
// all required properties in the StatusRecips call. The only
// thing we need to do is to fill in a specific per-recipient
// text description of the problem. It's good to have real
// info from the transport indicating the real cause for the
// failure
wsprintf (szHeaderText,
TEXT("\tThe WINDS transport service failed to deliver the message to this recipient.\r\n"
"\tRecipient Address: Server: %s Mailbox: %s. Delivery error: %#08X. %s"),
(pszServer ? pszServer : ""),
(pszMailbox ? pszMailbox : ""),
hUploadError,
(fErrorInServer ? TEXT("The error occurred in the server host.") :
TEXT("The error occurred in local processing.")));
if (fErrorInServer && hUploadError)
{
DWORD dwServerErrorIDS = 0;
switch (hUploadError)
{
case HRESULT_FROM_WIN32(ERROR_INVALID_ACCOUNT_NAME) :
dwServerErrorIDS = IDS_DELIVERY_ERROR_INVALID_ACCT;
break;
case HRESULT_FROM_WIN32(ERROR_NO_SUCH_USER) :
dwServerErrorIDS = IDS_DELIVERY_ERROR_NO_SUCH_USER;
break;
case HRESULT_FROM_WIN32 (ERROR_HOST_UNREACHABLE) :
dwServerErrorIDS = IDS_DELIVERY_ERROR_OFFLINE_SERVER;
break;
case HRESULT_FROM_WIN32 (ERROR_INVALID_ADDRESS) :
dwServerErrorIDS = IDS_DELIVERY_ERROR_INVALID_ADDRESS;
break;
case E_OUTOFMEMORY :
dwServerErrorIDS = IDS_DELIVERY_ERROR_OUTOFMEMORY;
break;
case E_INVALIDARG :
dwServerErrorIDS = IDS_DELIVERY_ERROR_INVALID_PARAM;
break;
}
if (!dwServerErrorIDS && FACILITY_STORAGE == HRESULT_FACILITY(hUploadError))
{
dwServerErrorIDS = IDS_DELIVERY_ERROR_ISTORAGE;
}
if (!dwServerErrorIDS && FACILITY_WIN32 == HRESULT_FACILITY(hUploadError))
{
dwServerErrorIDS = IDS_DELIVERY_ERROR_WIN32;
}
if (dwServerErrorIDS)
{
TCHAR szBuffer[256];
if (LoadString (m_hInstance, dwServerErrorIDS, szBuffer, 255))
{
lstrcat (szHeaderText, TEXT("\r\n\t"));
lstrcat (szHeaderText, szBuffer);
}
}
}
LPTSTR pStr;
hResult = gpfnAllocateMore (Cbtszsize(szHeaderText), pProps, (LPVOID *)&pStr);
if (SUCCEEDED(hResult))
{
// Copy the formatted string and hook it into the
// pre-allocated (by MAPI) column
lstrcpy (pStr, szHeaderText);
pProps[RECIP_REPORT_TEXT].ulPropTag = PR_REPORT_TEXT;
pProps[RECIP_REPORT_TEXT].Value.LPSZ = pStr;
}
else
{
pProps[RECIP_REPORT_TEXT].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_REPORT_TEXT));
pProps[RECIP_REPORT_TEXT].Value.err = hResult;
TraceResult ("CXPLogon::SendMailMessage: memory allocation failed for the NDR report string", hResult);
}
}
else
{
// For delivery report, each recipient must have this property set.
// Otherwise the spooler will default to generate an NDR instead.
pProps[RECIP_DELIVER_TIME].ulPropTag = PR_DELIVER_TIME;
pProps[RECIP_DELIVER_TIME].Value.ft = ft;

pProps[RECIP_REPORT_TEXT].ulPropTag = PROP_TAG (PT_ERROR, PROP_ID (PR_REPORT_TEXT));
pProps[RECIP_REPORT_TEXT].Value.err = S_OK;
}

// Based on the result of the submission to the remote host we determine into
// which address list to add this recipient
LPADRLIST * ppTmpList = (fSentSuccessfully ? &pAdrList : &pAdrListFailed);
ULONG ulTmpCount = (fSentSuccessfully ? ulCount1 : ulCount2);

// Does the list where this recipient goes have enough room for one more entry?
// If not, resize the address list to hold QUERY_SIZE more entries.
if (!(*ppTmpList) || ((*ppTmpList)->cEntries + 1 > ulTmpCount))
{
hResult= GrowAddressList (ppTmpList, 10, &ulTmpCount);
if (hResult)
{
goto ErrorExit;
}
ulCount1 = (fSentSuccessfully ? ulTmpCount : ulCount1);
ulCount2 = (!fSentSuccessfully ? ulTmpCount : ulCount2);
}

// We have room now so store the new ADRENTRY. As part of the
// storage, we're going to copy the SRow pointer from the SRowSet
// into the ADRENTRY. Once we've done this, we won't need the
// SRowSet any more ... and the SRow will be released when
// we unwind the ADRLIST
(*ppTmpList)->aEntries[(*ppTmpList)->cEntries].cValues = pRecipRows->aRow[ulRow].cValues;
(*ppTmpList)->aEntries[(*ppTmpList)->cEntries].rgPropVals = pRecipRows->aRow[ulRow].lpProps;

// Increase the number of entries in the address list
(*ppTmpList)->cEntries++;

// Now that we are finished with this row (it is in the right
// adrlist) we want to disassociate it from the rowset
// so we don't delete this before we modify the recipients list
pRecipRows->aRow[ulRow].lpProps = NULL;
pRecipRows->aRow[ulRow].cValues = 0;

}

// Let the MAPI spooler do other things
CheckSpoolerYield();

// Do we have some recipients that the message arrived to?
if (pAdrList)
{
hResult = pMsgObj->ModifyRecipients (MODRECIP_MODIFY, pAdrList);
TraceResult ("CXPLogon::SendMailMessage: ModifyRecipients failed (DELIVERED)", hResult);
hResult = S_OK; // We'll drop the error code from the modify recipients call
if (fNeedDeliveryReport)
{
hResult = m_pSupObj->StatusRecips (pMsgObj, pAdrList);
TraceResult ("CXPLogon::SendMailMessage: StatusRecips (DR) failed", hResult);
if (!HR_FAILED(hResult))
{
// If we were successful, we should null out the pointer becase MAPI released
// the memory for this structure. And we should not try to release it
// again in the cleanup code.
pAdrList = NULL;
}
}
}
// Do we have some recipients that the message DID NOT arrived to?
if (pAdrListFailed)
{
hResult = pMsgObj->ModifyRecipients (MODRECIP_MODIFY, pAdrListFailed);
// We'll drop the error code from the modify recipients call
TraceResult ("CXPLogon::SendMailMessage: ModifyRecipients failed (NON-DELIVERED)", hResult);

// The address list has the entries with the PR_RESPONSIBILITY set, so the
// spooler will know if it has to generate NDR reports.
hResult = m_pSupObj->StatusRecips (pMsgObj, pAdrListFailed);
TraceResult ("CXPLogon::SendMailMessage: StatusRecips (NDR) failed", hResult);
if (!HR_FAILED(hResult))
{
// If we were successful, we should null out the pointer becase MAPI released
// the memory for this structure. And we should not try to release it
// again in the cleanup code.
pAdrListFailed = NULL;
}
}

ErrorExit:
gpfnFreeBuffer (pProblems);
gpfnFreeBuffer (pMsgProps);
FreePadrlist (pAdrList);
FreePadrlist (pAdrListFailed);

// Release used objects
if (pTNEFObj)
{
pTNEFObj->Release();
}
if (pStream)
{
pStream->Release();
}
// Close the file if it has been opened
CloseHandle (hMsgFile);

// Delete the message file in case the function fails before the call to CreateFile() above
DeleteFile (szTmpFile);

// Call the sender function will NULL parameters to reset internal data buffers
if (lstrlen(szConnectInfo))
{
FinishUploadConnection (m_szServer, szConnectInfo);
}

// Once we have sent all messages to the remote server(s), terminate all connections
TerminateRemoteConnections();

// Let the MAPI spooler do other things
CheckSpoolerYield();

return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::SetOutgoingProps()
//
// Parameters
// pMsgObj Pointer to the IMessage object we are processing.
// ft FILETIME structure with the time the message is
// being delivered.
//
// Purpose
// This function checks that the sender information is on the message
// so that we may get replies from the intended recipients.
//
// Return Value
// None.
//
void WINAPI CXPLogon::SetOutgoingProps (LPMESSAGE pMsgObj, FILETIME ft)
{
LPSPropValue pSender;
ULONG ulValues;
HRESULT hResult = pMsgObj->GetProps ((LPSPropTagArray)&sptOutMsgProps, fMapiUnicode, &ulValues, &pSender);
if (FAILED(hResult))
{
TraceResult ("CXPLogon::SetOutgoingProps: Failed to get properties from the message", hResult);
pSender = NULL; // So that we may recover and continue using default values
}
ASSERT (2 == ulValues);
#define NUM_OUTGOING_PROPS 11
SPropValue spvProps[NUM_OUTGOING_PROPS] = { 0 };
ULONG i = 0;
// If no sender has been stamped on the message use the identity of the transport
if (!pSender || PR_SENDER_ENTRYID != pSender[0].ulPropTag)
{
spvProps[i].ulPropTag = PR_SENDER_NAME;
spvProps[i++].Value.LPSZ = m_UserInfo.szFullName;

spvProps[i].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
spvProps[i++].Value.LPSZ = m_szAddress;

spvProps[i].ulPropTag = PR_SENDER_ADDRTYPE;
spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;

spvProps[i].ulPropTag = PR_SENDER_ENTRYID;
spvProps[i++].Value.bin = m_pIdentityProps[XPID_EID].Value.bin;

spvProps[i].ulPropTag = PR_SENDER_SEARCH_KEY;
spvProps[i++].Value.bin = m_pIdentityProps[XPID_SEARCH_KEY].Value.bin;
}
// The MS Exchange mail viewer requires these properties
if (!pSender || PR_SENT_REPRESENTING_NAME != pSender[1].ulPropTag)
{
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_NAME;
spvProps[i++].Value.LPSZ = m_UserInfo.szFullName;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
spvProps[i++].Value.bin = m_pIdentityProps[XPID_SEARCH_KEY].Value.bin;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
spvProps[i++].Value.bin = m_pIdentityProps[XPID_EID].Value.bin;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
spvProps[i++].Value.LPSZ = m_szAddress;
}
gpfnFreeBuffer (pSender);

// Set the time when this transport actually transmitted the message
spvProps[i].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
spvProps[i++].Value.ft = ft;
spvProps[i].ulPropTag = PR_PROVIDER_SUBMIT_TIME;
spvProps[i++].Value.ft = ft;

ASSERT (i <= NUM_OUTGOING_PROPS);
hResult = pMsgObj->SetProps (i, spvProps, NULL);
TraceResult ("CXPLogon::SetOutgoingProps: Failed to set properties in the message", hResult);
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::SetIncomingProps()
//
// Parameters
// pMsgObj Pointer to the IMessage object we are processing.
//
// Purpose
// This function sets the PR_SENT_REPRESENTING_xxx properties in an
// incoming message if those properties aren't already in the message.
// For these properties we use the values for the sender of the message.
// We also walk the recipient list restricted to the address type of our
// transport looking for us, the recipient of the message, s that we may
// set the PR_MESSAGE_TO_xxx and PR_MESSAGE_RECIP_ME properties in
// the message.
//
// Return Value
// An HRESULT
//
HRESULT WINAPI CXPLogon::SetIncomingProps (LPMESSAGE pMsgObj, PLIST_NODE pNode)
{
// This is the maximum number of properties that we could set in the message
#define NUM_INCOMING_PROPS 28
SPropValue spvProps[NUM_INCOMING_PROPS] = { 0 };
ULONG ulValues, i = 0;
BOOL fNameIsMissing = FALSE, fAddressIsMissing = FALSE;
TCHAR szUnknown[] = TEXT("Unknown"), szAddress[128];
TCHAR szUnknownAddress[] = TEXT("\\\\Unknown\\Unknown"), szSender[MAX_STRING_SIZE+1];
LPTSTR pszAlias, pszServer;
LPSPropValue pProps = NULL, pSenderSKEY = NULL, pSenderEID = NULL, pObjProps = NULL;
LPVOID lpToBeFreed = NULL;

HRESULT hResult = pMsgObj->GetProps ((LPSPropTagArray)&sptNewMsgProps, fMapiUnicode, &ulValues, &pProps);
if (FAILED(hResult))
{
TraceResult ("CXPLogon::SetIncomingProps: Failed to get the message changes", hResult);
goto RecoverAndContinue;
}
hResult = S_OK; // Get rid of any warnings
// These properties should ALWAYS be in an incoming message, but just in case, we have a backup plan.
if ((PR_SENDER_NAME != pProps[NEW_SENDER_NAME].ulPropTag ||
PR_SENDER_EMAIL_ADDRESS != pProps[NEW_SENDER_EMAIL].ulPropTag) && !pNode->fRetry)
{
pNode->fRetry = TRUE;
m_List.ReQueueNode (pNode);
gpfnFreeBuffer (pProps);
return MAPI_E_CORRUPT_DATA;
}

if (PR_SENDER_NAME != pProps[NEW_SENDER_NAME].ulPropTag)
{
fNameIsMissing = TRUE;
pProps[NEW_SENDER_NAME].ulPropTag = PR_SENDER_NAME;
if (PR_SENDER_EMAIL_ADDRESS == pProps[NEW_SENDER_EMAIL].ulPropTag)
{
// Decompose the address and get the alias. At least this is better that UNKNOWN!
lstrcpy (szAddress, pProps[NEW_SENDER_EMAIL].Value.LPSZ);
DecomposeAddress (szAddress, &pszServer, &pszAlias);
lstrcpy (szSender, pszAlias);
pProps[NEW_SENDER_NAME].Value.LPSZ = szSender;
}
else
{
// This is the best we can do.
pProps[NEW_SENDER_NAME].Value.LPSZ = szUnknown;
}
}
if (PR_SENDER_EMAIL_ADDRESS != pProps[NEW_SENDER_EMAIL].ulPropTag)
{
pProps[NEW_SENDER_EMAIL].ulPropTag = PR_SENDER_EMAIL_ADDRESS;
pProps[NEW_SENDER_EMAIL].Value.LPSZ = szUnknownAddress;
fAddressIsMissing = TRUE;
}
spvProps[i].ulPropTag = PR_SENDER_ADDRTYPE;
spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;

// Looks like ABWDS is around, mark the internal flag.
// ABWDS is the native WINDS address book service provider. If present,
// instead of creating a one-off entry for the sender, we
// will simply open the entry in the WINDS address book and get the
// necessary properties out from it.
// Note that in other to open an entry in the ABWDS address book provider
// we need to have at least the email address of the sender, otherwise we
// will default to create one-off entries in the sender (and delagate sender)
if (m_fABWDSInstalled && !fAddressIsMissing)
{
PRIVATE_XP_ENTRYID eidEntry = { 0 };
eidEntry.uidGlobal = guidXPABEntries;
eidEntry.uidWINDSEntries = guidABEntries;
// Decompose the address and get the alias.
DecomposeAddress (pProps[NEW_SENDER_EMAIL].Value.LPSZ, &pszServer, &pszAlias);
lstrcpy (eidEntry.szObjectAlias, pszAlias);
// Put the Email address back to what it was
RecomposeAddress (pszServer, pszAlias, pProps[NEW_SENDER_EMAIL].Value.LPSZ);
LPMAILUSER pUser = NULL;
ULONG ulObjType;
const static SizedSPropTagArray (3, sptWINDSObjProps) =
{
3,
{
PR_DISPLAY_NAME,
PR_SEARCH_KEY,
PR_ENTRYID
}
};

HRESULT hTmpResult = m_pSupObj->OpenEntry (CB_PRIVATE_XP_EID,
(LPENTRYID)&eidEntry,
NULL,
0,
&ulObjType,
(LPUNKNOWN *)&pUser);
if (!hTmpResult)
{
hTmpResult = pUser->GetProps ((LPSPropTagArray)&sptWINDSObjProps,
fMapiUnicode,
&ulValues,
&pObjProps);
if (SUCCEEDED(hTmpResult)) // We might get warnings
{
if (PR_DISPLAY_NAME == pObjProps[0].ulPropTag &&
PR_SEARCH_KEY == pObjProps[1].ulPropTag &&
PR_ENTRYID == pObjProps[2].ulPropTag)
{
if (fNameIsMissing)
{
spvProps[i].ulPropTag = PR_SENDER_NAME;
spvProps[i++].Value.LPSZ = pObjProps[0].Value.LPSZ;
}
spvProps[i].ulPropTag = PR_SENDER_SEARCH_KEY;
spvProps[i++].Value.bin = pObjProps[1].Value.bin;
spvProps[i].ulPropTag = PR_ENTRYID;
spvProps[i++].Value.bin = pObjProps[2].Value.bin;
if (PR_SENT_REPRESENTING_NAME != pProps[NEW_SENT_NAME].ulPropTag &&
PR_SENT_REPRESENTING_EMAIL_ADDRESS != pProps[NEW_SENT_EMAIL].ulPropTag)
{
// If the message doesn't have PR_SENT_REPRESENTING_xxx use the sender
// properties for the delagate sender properties
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_NAME;
spvProps[i++].Value.LPSZ = pObjProps[0].Value.LPSZ;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
spvProps[i++].Value.bin = pObjProps[1].Value.bin;

spvProps[i].ulPropTag    = PR_SENT_REPRESENTING_ENTRYID; 
spvProps[i++].Value.bin = pObjProps[2].Value.bin;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
spvProps[i++].Value.LPSZ = pProps[NEW_SENDER_EMAIL].Value.LPSZ;
}
}
else
{
// We couldn't find the properties needed from the WINDS
// address book entry. Continue with one-off entries
hTmpResult = MAPI_W_ERRORS_RETURNED;
}
}
pUser->Release();
}
if (S_OK == hTmpResult)
{
goto ContinueAddingProps;
}
}

// We need to make a search key for this recipient.
spvProps[i].ulPropTag = PR_SENDER_SEARCH_KEY;
hResult = MakeSearchKey (pProps,
pProps[NEW_SENDER_EMAIL].Value.LPSZ,
&spvProps[i].Value.bin.cb,
&spvProps[i].Value.bin.lpb);
if (!hResult)
{
pSenderSKEY = &spvProps[i];
i++;
// We need to create a one-off entry in the address book so that we may
// reply to this recipient. If the recipient already exist, its
// entry Id will be returned instead.
spvProps[i].ulPropTag = PR_SENDER_ENTRYID;

//else
{
hResult = m_pSupObj->CreateOneOff (pProps[NEW_SENDER_NAME].Value.LPSZ,
WINDS_ADDRESS_TYPE,
pProps[NEW_SENDER_EMAIL].Value.LPSZ,
fMapiUnicode,
&spvProps[i].Value.bin.cb,
(LPENTRYID *)&spvProps[i].Value.bin.lpb);
}
if (hResult)
{
i--; // Ignore this property and the previous
}
else
{
pSenderEID = &spvProps[i];
i++;
}
}
TraceResult ("CXPLogon::SetIncomingProps: Failed to create a one-off for the sender", hResult);

if (PR_SENT_REPRESENTING_NAME == pProps[NEW_SENT_NAME].ulPropTag &&
PR_SENT_REPRESENTING_EMAIL_ADDRESS == pProps[NEW_SENT_EMAIL].ulPropTag)
{
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
hResult = MakeSearchKey (pProps,
pProps[NEW_SENT_EMAIL].Value.LPSZ,
&spvProps[i].Value.bin.cb,
&spvProps[i].Value.bin.lpb);
if (!hResult)
{
i++;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
hResult = m_pSupObj->CreateOneOff (pProps[NEW_SENT_NAME].Value.LPSZ,
WINDS_ADDRESS_TYPE,
pProps[NEW_SENT_EMAIL].Value.LPSZ,
fMapiUnicode,
&spvProps[i].Value.bin.cb,
(LPENTRYID *)&spvProps[i].Value.bin.lpb);
if (hResult)
{
i--; // Ignore this property and the previous
}
else
{
lpToBeFreed = spvProps[i].Value.bin.lpb;
i++;
}
}
// We trace the error but is not fatal
TraceResult ("CXPLogon::SetIncomingProps: Failed to create a oneoff for the delegated sender", hResult);
}
else
{
// If the message doesn't have PR_SENT_REPRESENTING_xxx properties AND we were
// successful at creating the one-off for the sender, we set PR_SENT_REPRESENTING_xxx
// to the same values as PR_SENDER_xxx
if (!hResult)
{
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_NAME;
spvProps[i++].Value.LPSZ = pProps[NEW_SENDER_NAME].Value.LPSZ;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ADDRTYPE;
spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
spvProps[i++].Value.LPSZ = pProps[NEW_SENDER_EMAIL].Value.LPSZ;

ASSERT (PR_SENDER_ENTRYID == pSenderEID->ulPropTag);
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_ENTRYID;
spvProps[i++].Value = pSenderEID->Value;

ASSERT (PR_SENDER_SEARCH_KEY == pSenderSKEY->ulPropTag);
spvProps[i].ulPropTag = PR_SENT_REPRESENTING_SEARCH_KEY;
spvProps[i++].Value = pSenderSKEY->Value;
}
}

ContinueAddingProps:
// If the sender's address is the same as our address (the recipient), then the message
// was sent from us, to ourselves, therefore we must set the flag bit in the message.
if (0 == lstrcmpi (pProps[NEW_SENDER_EMAIL].Value.LPSZ, m_szAddress))
{
ASSERT (PR_MESSAGE_FLAGS == pProps[NEW_MSG_FLAGS].ulPropTag);
spvProps[i].ulPropTag = PR_MESSAGE_FLAGS;
spvProps[i++].Value.l = pProps[NEW_MSG_FLAGS].Value.l | MSGFLAG_FROMME;
}

RecoverAndContinue:
// We must stamp the message with this properties since we (this transport)
// were the ones who processed the incoming message.
spvProps[i].ulPropTag = PR_RECEIVED_BY_ENTRYID;
spvProps[i++].Value.bin = m_pIdentityProps[XPID_EID].Value.bin;
spvProps[i].ulPropTag = PR_RECEIVED_BY_NAME;
spvProps[i++].Value.LPSZ = m_pIdentityProps[XPID_NAME].Value.LPSZ;
spvProps[i].ulPropTag = PR_RECEIVED_BY_SEARCH_KEY;
spvProps[i++].Value.bin = m_pIdentityProps[XPID_SEARCH_KEY].Value.bin;
spvProps[i].ulPropTag = PR_RECEIVED_BY_ADDRTYPE;
spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
spvProps[i].ulPropTag = PR_RECEIVED_BY_EMAIL_ADDRESS;
spvProps[i++].Value.LPSZ = m_szAddress;

// In this transport we set PR_RCVD_xxx with the values of PR_RECEIVED_xxx
spvProps[i].ulPropTag = PR_RCVD_REPRESENTING_ENTRYID;
spvProps[i++].Value.bin = m_pIdentityProps[XPID_EID].Value.bin;
spvProps[i].ulPropTag = PR_RCVD_REPRESENTING_NAME;
spvProps[i++].Value.LPSZ = m_pIdentityProps[XPID_NAME].Value.LPSZ;
spvProps[i].ulPropTag = PR_RCVD_REPRESENTING_SEARCH_KEY;
spvProps[i++].Value.bin = m_pIdentityProps[XPID_SEARCH_KEY].Value.bin;
spvProps[i].ulPropTag = PR_RCVD_REPRESENTING_ADDRTYPE;
spvProps[i++].Value.LPSZ = WINDS_ADDRESS_TYPE;
spvProps[i].ulPropTag = PR_RCVD_REPRESENTING_EMAIL_ADDRESS;
spvProps[i++].Value.LPSZ = m_szAddress;

SYSTEMTIME st;
FILETIME ftTime;
GetSystemTime (&st);
SystemTimeToFileTime (&st, &ftTime);
spvProps[i].ulPropTag = PR_MESSAGE_DOWNLOAD_TIME;
spvProps[i++].Value.ft = ftTime;

LPMAPITABLE pTable;
LPSRowSet pRows;
BOOL fFoundCC, fFoundTO;
fFoundCC = fFoundTO = FALSE;
LPSPropValue pRecip;
SPropValue spvFilter = { 0 };
spvFilter.ulPropTag = PR_ADDRTYPE;
spvFilter.Value.LPSZ = WINDS_ADDRESS_TYPE;

SRestriction srRecips = { 0 };
srRecips.rt = RES_PROPERTY;
srRecips.res.resProperty.relop = RELOP_EQ;
srRecips.res.resProperty.ulPropTag = PR_ADDRTYPE;
srRecips.res.resProperty.lpProp = &spvFilter;

hResult = pMsgObj->GetRecipientTable (fMapiUnicode, &pTable);
if (!hResult)
{
hResult = HrQueryAllRows (pTable,
(LPSPropTagArray)&sptMsgRecipProps,
&srRecips,
NULL,
0,
&pRows);
if (!hResult)
{
ASSERTMSG (pRows->cRows > 0, "Huh?, Zero rows?");
for (ULONG ulIndex=0; ulIndex<pRows->cRows; ulIndex++)
{
pRecip = pRows->aRow[ulIndex].lpProps;
ASSERTMSG (PR_EMAIL_ADDRESS == pRecip[0].ulPropTag, "Where is the PR_EMAIL_ADDRESS?");
ASSERTMSG (PR_RECIPIENT_TYPE == pRecip[1].ulPropTag, , "Where is the PR_RECIPIENT_TYPE?");
if (PR_EMAIL_ADDRESS == pRecip[0].ulPropTag)
{
// We use the address for the comparison because the sender might
// have create a one-off to send us this message and the only thing that this
// transport can count on being the same across all computers
// is the email address. The display name is modifyable by the user.
if (0 == lstrcmpi (pRecip[0].Value.LPSZ, m_szAddress))
{
if (PR_RECIPIENT_TYPE == pRecip[1].ulPropTag)
{
if (MAPI_TO == pRecip[1].Value.l)
{
fFoundTO = TRUE;
}
else
{
if (MAPI_CC == pRecip[1].Value.l)
{
fFoundCC = TRUE;
}
}
}
}
}
}
FreeProws (pRows);
}
pTable->Release();
}
TraceResult ("CXPLogon::SetIncomingProps: Something went wrong setting PR_MESSAGE_xxx", hResult);

if (!hResult)
{
spvProps[i].ulPropTag = PR_MESSAGE_TO_ME;
spvProps[i++].Value.b = fFoundTO;

spvProps[i].ulPropTag = PR_MESSAGE_CC_ME;
spvProps[i++].Value.b = fFoundCC;

spvProps[i].ulPropTag = PR_MESSAGE_RECIP_ME;
spvProps[i++].Value.b = fFoundTO || fFoundCC;
}
ASSERT (i <= NUM_INCOMING_PROPS);
hResult = pMsgObj->SetProps (i, spvProps, NULL);
TraceResult ("CXPLogon::SetIncomingProps: Failed to set properties in the message", hResult);
gpfnFreeBuffer(lpToBeFreed);
if(pSenderEID)
gpfnFreeBuffer(pSenderEID->Value.bin.lpb);
gpfnFreeBuffer (pProps);
gpfnFreeBuffer (pObjProps);
return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::GetMsgTempFileName()
//
// Parameters
// pszFileName Pointer to a buffer allocated by the caller where the
// function returns a fully qualified path and file for
// a uniquely named temporary file.
//
// Purpose
// This function creates a temporary file name. The file name will be
// returned in the pszFileName buffer which must have been allocated by
// the caller. The file will have a fully qualified path to its location.
// The location of the file is on the TEMP directory, as set in the system,
// and within it, in the directory for the downloads of the WINDS
// message transport
//
// Return Value
// TRUE if the function is successful at creating a temporary
// unique file name. FALSE otherwise.
//
BOOL WINAPI CXPLogon::GetMsgTempFileName (LPTSTR pszFileName)
{
TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH];
// Ask the system for the TEMP directory
DWORD dwChars = GetTempPath (_MAX_PATH, szTmpDir);
if (dwChars)
{
lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, m_UserInfo.szMailboxName);
dwChars = ::GetTempFileName (szDownloadDir, // Call the Win32 API
XP_MSG_FILE_PREFIX, // Our transport's fixed prefix for temp files
0, // Use the time to create a pseudo-random number
pszFileName); // Destination buffer
}
if (!dwChars)
{
TraceResult ("CXPLogon::GetMsgTempFileName: Failed to get temp path or file name", GetLastError());
}
return (0 != dwChars ? TRUE : FALSE);
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::CreateMsgHeaderTextLine()
//
// Parameters
// pProps Array of properties of the message being
// submitted where we get the information to create a
// message header string to send to the server
// pszBuffer Buffer where the function copies the formated data
// for the server message header string
// time Reference to a FILETIME structure with the time
// the transport is delivering the message.
//
// Purpose
// This function creates a string with the data needed in the server
// side to update the remote server mailboxes message header file of
// the recipients.
//
// Return Value
// None
//
void WINAPI CXPLogon::CreateMsgHeaderTextLine (LPSPropValue pProps,
LPTSTR pszBuffer,
FILETIME & time)
{
// In case we don't find the properties in the array, we need
// to supply default values for the strings. This method
// should never fail.
LPTSTR pszSubject = TEXT("<Subject Not Found>");
LPTSTR pszMsgClass = TEXT("IPM.Note");
TCHAR szDisplayTo[256] = { 0 };

// In case we don't have the expected properties, assign default values.
long lMsgFlags = 0;
if (PR_MESSAGE_FLAGS == pProps[MSG_FLAGS].ulPropTag)
{
// Filter out the flags to only allow the ones listed
lMsgFlags = pProps[MSG_FLAGS].Value.l & (MSGFLAG_UNMODIFIED |
MSGFLAG_HASATTACH |
MSGFLAG_FROMME |
MSGFLAG_RESEND);
}
if (pProps[MSG_SIZE].ulPropTag != PR_MESSAGE_SIZE)
{
pProps[MSG_SIZE].Value.l = 1024; // Default to 1Kb
}
if (pProps[MSG_PRIORITY].ulPropTag != PR_PRIORITY)
{
pProps[MSG_PRIORITY].Value.l = PRIO_NORMAL;
}
if (pProps[MSG_IMPORTANCE].ulPropTag != PR_IMPORTANCE)
{
pProps[MSG_IMPORTANCE].Value.l = IMPORTANCE_NORMAL;
}
if (pProps[MSG_SENSITIVITY].ulPropTag != PR_SENSITIVITY)
{
pProps[MSG_SENSITIVITY].Value.l = SENSITIVITY_NONE;
}

// Get the string of TO recipients. If the string is too long, set the elipsis at the end of the buffer
if (pProps[MSG_DISP_TO].ulPropTag == PR_DISPLAY_TO)
{
LPTSTR pszDisplayTo = pProps[MSG_DISP_TO].Value.LPSZ;
if (lstrlen (pszDisplayTo) > 256)
{
pszDisplayTo[252] = '.'; // Copy the elipsis for a long string
pszDisplayTo[253] = '.';
pszDisplayTo[254] = '.';
pszDisplayTo[255] = 0;
}
LPTSTR pszOneName = strtok (pszDisplayTo, ";");
if (pszOneName)
{
pszOneName[lstrlen(pszOneName) - 1] = 0;
lstrcpy (szDisplayTo, &pszOneName[1]);
}
while (pszOneName)
{
pszOneName = strtok (NULL, ";");
if (pszOneName)
{
pszOneName[0] = TEXT(';');
pszOneName[1] = TEXT(' ');
pszOneName[lstrlen(pszOneName) - 1] = 0;
lstrcat (szDisplayTo, pszOneName);
}
}
}
if (0 == szDisplayTo[0])
{
lstrcpy (szDisplayTo, TEXT("<Display To Names Not Found>"));
}
// Get the message class string.
if (pProps[MSG_CLASS].ulPropTag == PR_MESSAGE_CLASS)
{
pszMsgClass = pProps[MSG_CLASS].Value.LPSZ;
}
// Get the subject. If the string is too long, set the elipsis at the end of the buffer
if (pProps[MSG_SUBJECT].ulPropTag == PR_SUBJECT)
{
pszSubject = pProps[MSG_SUBJECT].Value.LPSZ;
if (lstrlen (pszSubject) > 256)
{
pszSubject[252] = '.'; // Copy the elipsis for a long string
pszSubject[253] = '.';
pszSubject[254] = '.';
pszSubject[255] = 0;
}
}
// Write the properties into the buffer. This buffer is the header
// information we send to the server The buffer passed in must hold
// at least 1024 characters
wsprintf (pszBuffer,
// The string must end with a ',' (for the parsing logic
// in the downloading of the headers)
TEXT("%d,%s,%d,%s,%d,%s,%s,%d,%d,%d,%d,%d,%d,%d,"),
lstrlen(m_UserInfo.szFullName),
m_UserInfo.szFullName,
lstrlen(szDisplayTo),
szDisplayTo,
lstrlen(pszSubject),
pszSubject,
pszMsgClass,
lMsgFlags,
pProps[MSG_SIZE].Value.l,
pProps[MSG_PRIORITY].Value.l,
pProps[MSG_IMPORTANCE].Value.l,
pProps[MSG_SENSITIVITY].Value.l,
time.dwLowDateTime,
time.dwHighDateTime);
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::IsValidAddress()
//
// Parameters
// pszAddress The address of the recipient. This string gets
// decomposed locally, so it can't be re-used by
// the caller.
// ppszServer Pointer to a location where the function returns
// the remote server name
// ppszMailbox Pointer to a location where the function returns
// the remote user mailbox name
//
// Purpose
// This functions takes the address of a recipient and makes sure it is
// valid (as far as syntax) and decomposes the address into two
// components: Remote server name and Remote user mailbox name. The
// two component strings are returned to the caller.
//
// Return Value
// TRUE if the address of the recipient is valid. FALSE otherwise.
//
BOOL WINAPI CXPLogon::IsValidAddress (LPTSTR pszAddress,
LPTSTR * ppszServer,
LPTSTR * ppszMailbox)
{
// Make sure that we can read the string
if (!pszAddress || IsBadStringPtr (pszAddress, 32))
{
TraceMessage ("CXPLogon::IsValidAddress: Invalid string pointer");
return FALSE;
}
DecomposeAddress (pszAddress, ppszServer, ppszMailbox);
// Validate the names of the server and the mailbox
if (!IsValidServerName (*ppszServer) || !*ppszMailbox)
{
TraceMessage ("CXPLogon::IsValidAddress: Invalid address for remote server host");
return FALSE;
}
return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::GrowAddressList()
//
// Parameters
// ppAdrList Pointer to an address where the old address list
// is and where the new resized address list will
// be returned
// ulResizeBy Number of new address entries to add to the list
// pulOldAndNewCount Number of entries in the old address list. In
// this parameter, upon sucessful return, will have
// the number of in the new address list
//
// Purpose
// In this function, given an address list with pulOldAndNewCount of
// entries, we resize the address list to hold the old number of
// entries plus the ulResizeBy entries. The old address list contents
// are copied to the new list and the count reset. The memory for the
// old address list is released here.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::GrowAddressList (LPADRLIST * ppAdrList,
ULONG ulResizeBy,
ULONG * pulOldAndNewCount)
{
LPADRLIST pNewAdrList;
// Calculate how big the new buffer for the expanded address list should be
ULONG cbSize = CbNewADRLIST ((*pulOldAndNewCount) + ulResizeBy);
// Allocate the memory for it
HRESULT hResult = gpfnAllocateBuffer (cbSize, (LPVOID *)&pNewAdrList);
if (hResult)
{
// We can't continue
TraceResult ("CXPLogon::GrowAddressList: Failed to allocate memory for resized address list", hResult);
return hResult;
}

// Zero-out all memory for neatness
ZeroMemory (pNewAdrList, cbSize);

// If we had entries in the old address list, copy the memory from
// the old addres list into the new expanded list
if ((*pulOldAndNewCount))
{
CopyMemory (pNewAdrList, *ppAdrList, CbNewADRLIST ((*pulOldAndNewCount)));
}

// Set the number of entries in the new address list to the OLD size
pNewAdrList->cEntries = (*pulOldAndNewCount);

// We must return the number of available entries in the new expanded address list
(*pulOldAndNewCount) += ulResizeBy;

// Free the old memory and put the new pointer in place
gpfnFreeBuffer (*ppAdrList);
*ppAdrList = pNewAdrList;
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::CheckSpoolerYield()
//
// Parameters
// fReset Resets the pseudo-timer
//
// Purpose
// Enforce the 0.2 second rule for transport that need to yield to the
// MAPI spooler. Called periodically while processing a message to
// determine if we have used more than 0.2 seconds. If so, then call
// SpoolerYield(), else just continue.
// This is called with fReset set to TRUE when we first enter one
// of the Transport Logon methods (usually one that is known to
// take a long time like StartMessage() or SubmitMessage(). )
//
// Return Value
// None.
//
void WINAPI CXPLogon::CheckSpoolerYield (BOOL fReset)
{
DWORD dwStop;
static DWORD dwStart;
if (fReset)
{
dwStart = GetTickCount();
}
else
{
dwStop = GetTickCount();
if ((dwStop - dwStart) > 200) // 200 milliseconds
{
m_pSupObj->SpoolerYield (0);
dwStart = GetTickCount();
}
}
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::InitializeStatusRow()
//
// Parameters
// ulFlags 0 if the properties are being created the first time.
// MODIFY_FLAGS if a change is being made to the properties
//
// Purpose
// To initialize or modify the status properties of a CXPLogon
// object. This function allocates an array with NUM_STATUS_ROW_PROPS
// properties and initializes them.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::InitializeStatusRow (ULONG ulFlags)
{
#define NUM_STATUS_ROW_PROPS 10
SPropValue spvStatusRow[NUM_STATUS_ROW_PROPS] = { 0 };
ULONG i = 0;

///////////////////////////////////////////////////////////////////////////
// Set the PR_PROVIDER_DISPLAY property: The transport readable name
spvStatusRow[i].ulPropTag = PR_PROVIDER_DISPLAY;
spvStatusRow[i++].Value.LPSZ = TRANSPORT_DISPLAY_NAME_STRING;

///////////////////////////////////////////////////////////////////////////
// Set the PR_RESOURCE_METHODS property. These are the methods implemented
// in the our IMAPIStatus implementation (CMAPIStatus class.)
spvStatusRow[i].ulPropTag = PR_RESOURCE_METHODS;
// we support ALL the methods in our implementation of IMAPIStatus interface (except the WRITABLE ones)
spvStatusRow[i++].Value.l = STATUS_SETTINGS_DIALOG |
STATUS_FLUSH_QUEUES |
STATUS_VALIDATE_STATE |
STATUS_CHANGE_PASSWORD;

///////////////////////////////////////////////////////////////////////////
// Set the PR_STATUS_CODE property.
spvStatusRow[i].ulPropTag = PR_STATUS_CODE;
spvStatusRow[i++].Value.l = GetTransportStatusCode();

///////////////////////////////////////////////////////////////////////////
// Set the PR_STATUS_STRING property
TCHAR szStatus[64];
LoadStatusString (szStatus, sizeof(szStatus));
spvStatusRow[i].ulPropTag = PR_STATUS_STRING;
spvStatusRow[i++].Value.LPSZ = szStatus;

///////////////////////////////////////////////////////////////////////////
// Set the PR_DISPLAY_NAME property
TCHAR szDisplayName[64];
wsprintf (szDisplayName, TEXT("%s (%s)"), TRANSPORT_DISPLAY_NAME_STRING, m_szServer);
spvStatusRow[i].ulPropTag = PR_DISPLAY_NAME;
spvStatusRow[i++].Value.LPSZ = szDisplayName;

///////////////////////////////////////////////////////////////////////////
// Set the PR_IDENTITY_ENTRYID property
spvStatusRow[i].ulPropTag = PR_IDENTITY_ENTRYID;
spvStatusRow[i++].Value = m_pIdentityProps[XPID_EID].Value;

///////////////////////////////////////////////////////////////////////////
// Set the PR_IDENTITY_DISPLAY property
spvStatusRow[i].ulPropTag = PR_IDENTITY_DISPLAY;
spvStatusRow[i++].Value.LPSZ = m_pIdentityProps[XPID_NAME].Value.LPSZ;

///////////////////////////////////////////////////////////////////////////
// Set the PR_IDENTITY_SEARCH_KEY property
spvStatusRow[i].ulPropTag = PR_IDENTITY_SEARCH_KEY;
spvStatusRow[i++].Value = m_pIdentityProps[XPID_SEARCH_KEY].Value;

///////////////////////////////////////////////////////////////////////////
// Set the PR_REMOTE_PROGRESS property
spvStatusRow[i].ulPropTag = PR_REMOTE_PROGRESS;
spvStatusRow[i++].Value.l = -1; // Not initialized

///////////////////////////////////////////////////////////////////////////
// Set the PR_REMOTE_VALIDATE_OK property
spvStatusRow[i].ulPropTag = PR_REMOTE_VALIDATE_OK;
spvStatusRow[i++].Value.b = TRUE;
ASSERT (NUM_STATUS_ROW_PROPS == i);

// Write the entries on the provider's session status row
HRESULT hResult = m_pSupObj->ModifyStatusRow (i, spvStatusRow, ulFlags);
TraceResult ("CXPLogon::InitializeStatusRow: Failed to modify the status row", hResult);
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::CheckForUnfinishedDownloads()
//
// Parameters
// None.
//
// Purpose
// This function checks to see if we have files where downloaded messages
// were lefted from a previous session or from a previous failed download
// sequence. The message files, if any, should be in the data directory of
// this service. If any files are found, the message queue is loaded and
// the during out inbound logic initialization in
// IXPLogon::TransportNotify(), we check the queue. If the queue is not
// empty, we tell the transport to flush our inbound flush.
//
// Return Value
// None.
//
void WINAPI CXPLogon::CheckForUnfinishedDownloads()
{
WIN32_FIND_DATA wfdFile = { 0 };
TCHAR szTmpDir[_MAX_PATH], szDownloadDir[_MAX_PATH], szSearchMask[512];
GetTempPath (_MAX_PATH, szTmpDir);
lstrcat (szTmpDir, WINDS_DATA_DIRECTORY);
wsprintf (szDownloadDir, WINDS_DOWNLOAD_DIR_NAME_FORMAT, szTmpDir, m_UserInfo.szMailboxName);
wsprintf (szSearchMask, TEXT("%s\\%s*.%s"), szDownloadDir, XP_MSG_FILE_PREFIX, XP_MSG_FILE_SUFFIX);
HANDLE hSearch = FindFirstFile (szSearchMask, &wfdFile);
if (hSearch == INVALID_HANDLE_VALUE)
{
return;
}
BOOL fFound = TRUE;
while (fFound)
{
wsprintf (szTmpDir, TEXT("%s\\%s"), szDownloadDir, wfdFile.cFileName);
// If the file size of 0, then is probably some garbage file. Get rid of it.
// A zero-sized file does not contains a message.
if (0 == wfdFile.nFileSizeHigh && 0 == wfdFile.nFileSizeLow)
{
DeleteFile (szTmpDir);
}
else
{
m_List.QueuePendingMsgFile (szTmpDir);
}
fFound = FindNextFile (hSearch, &wfdFile);
}
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::DownloadMessageHeaders()
//
// Parameters
// None
//
// Purpose
// This function downloads the message headers for the user's mailbox and
// is a remote folder is available, re-load the contents table of the
// folder with the new message headers information.
//
// Return Value
// An HRESULT
//
STDMETHODIMP CXPLogon::DownloadMessageHeaders()
{
// Guard against re-entrancy from the timer call back which happens on a separate thread
UpdateProgress (0, REMOTE_ACTION_HEADER_REFRESH); // Start the progress bar
HRESULT hResult = GetHeadersFromServer (m_szServer,

m_UserInfo.szMailboxName, 
m_szHeaders);
UpdateProgress (50, REMOTE_ACTION_HEADER_REFRESH); // Half way through
if (!hResult && m_pStatusObj && m_pStatusObj->m_pHeaderFolder)
{
hResult = m_pStatusObj->m_pHeaderFolder->FillContentsTable (m_szHeaders);
}
UpdateProgress (100, REMOTE_ACTION_HEADER_REFRESH); // Finish
TraceResult ("CXPLogon::DownloadMessageHeaders", hResult);
return hResult;
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::EmptyInboundQueue()
//
// Parameters
// None
//
// Purpose
// Deletes all the node in the queue of downloaded messages, deleting
// the used memory and closing the handle to the local temporary
// message file.
//
// Return Value
// None
//
void WINAPI CXPLogon::EmptyInboundQueue()
{
// If we have nodes at this point, is because the transport could not
// process them at this time. They will be retrieved in the next time this
// transport comes on line on the same profile or on a profile that has
// the same user identity.
PLIST_NODE pNode = m_List.GetDownloadNode();
while (pNode)
{
CloseHandle (pNode->hFile);
delete pNode;
// If the list is empty, pNode will be NULL.
// CList::GetDownloadNode() returns NULL for an empty list.
pNode = m_List.GetDownloadNode();
}
}

///////////////////////////////////////////////////////////////////////////////
// TimerWndProc()
//
// Parameters
// { Refer to Win32 documentation }
//
// Purpose
// Stub window procedure for the hidden timer window class.
//
// Return Value
// Default value return by the default window procedure
//
LRESULT CALLBACK TimerWndProc (HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
return DefWindowProc(hWnd, message, wParam, lParam);
}


///////////////////////////////////////////////////////////////////////////////
// CXPLogon::InitializeTimer()
//
// Parameters
// None.
//
// Purpose
// Initialize the hidden window for the upload timer.
//
// Return Value
// None.
//
void WINAPI CXPLogon::InitializeTimer()
{
WNDCLASS wc = { 0 };
// Register the class for this application
wc.lpfnWndProc = TimerWndProc;
wc.hInstance = m_hInstance;
wc.lpszClassName = TIMER_WND_CLASS;
if (!RegisterClass (&wc))
{
TraceResult ("InitializeTimer: Failed to register the timer window class", GetLastError());
return;
}
m_hTimerWnd = CreateWindow (TIMER_WND_CLASS,
NULL,
WS_OVERLAPPEDWINDOW,
0, 0, 0, 0,
NULL,
NULL,
m_hInstance,
NULL);
if (NULL == m_hTimerWnd)
{
TraceResult ("InitializeTimer: Failed to create timer window", GetLastError());
return;
}
SetWindowLong (m_hTimerWnd, GWL_USERDATA, (LONG)this);
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::StartUploadTimer()
//
// Parameters
// None.
//
// Purpose
// This function sets a timer with the operating system. When
// the specified period elapses, we will get called in the procedure
// specified and we will then flush the incoming and outgoing queues.
// Note that we round the delivery time to the HOUR
//
// Return Value
// None.
//
void WINAPI CXPLogon::StartUploadTimer()
{
SYSTEMTIME stNow = { 0 };
GetLocalTime (&stNow);
WORD wUploadHour = m_stDelivTime.wHour;
// If the time is 0:00, this is 12:00 AM midnight
if (!wUploadHour)
{
wUploadHour = 24;
}
UINT uDelay = 0;
// How many hours before the next upload?
int nHours = wUploadHour - stNow.wHour;
if (nHours <= 0)
{
// If the hours is 0 and the time hasn't arrived with in this hour,
// set the upload timer for THIS hour.
if (0 == nHours && stNow.wMinute < m_stDelivTime.wMinute)
{
nHours = -24;
uDelay += (UINT)(m_stDelivTime.wMinute - stNow.wMinute) * 60 * 1000;
}
nHours += 24;
}
uDelay += (UINT)nHours * 60 * 60 * 1000; // Number of milliseconds in the hours
m_uTimerID = SetTimer (m_hTimerWnd, 0, uDelay, UploadTimerProc);
if (0 == m_uTimerID)
{
TraceResult ("CXPLogon::StartUploadTimer: Failed to set the timer", GetLastError());
}
}

///////////////////////////////////////////////////////////////////////////////
// CXPLogon::StopUploadTimer()
//
// Parameters
// None.
//
// Purpose
// Terminates the timer used for the upload time.
//
// Return Value
// None
//
void WINAPI CXPLogon::StopUploadTimer()
{
// If we have a timer around, terminate it and all its associated resources
if (m_uTimerID)
{
KillTimer (m_hTimerWnd, m_uTimerID);
DestroyWindow (m_hTimerWnd);
UnregisterClass (TIMER_WND_CLASS, m_hInstance);
m_hTimerWnd = NULL;
m_uTimerID = 0;
}
}

///////////////////////////////////////////////////////////////////////////////
// UploadTimerProc()
//
// Parameters
// { Refer to the Win32 TIMERPROC callback parameters }
//
// Purpose
// This function executes when the timer delay has expired.
// Now we ask the spooler to send us the deferred messages, and we set
// the transport state ready to receive updated headers.
//
// Return Value
// None
//
void CALLBACK UploadTimerProc (HWND hTimerWnd,
UINT Message,
UINT idEvent,
DWORD dwTime)
{
KillTimer (hTimerWnd, idEvent);
// We asked to get the CXPLogon object who set the timer. We now
// check and see if the object is valid before we attempt to use it.
CXPLogon * pLogon = (CXPLogon *)GetWindowLong (hTimerWnd, GWL_USERDATA);
if (!pLogon ||
IsBadWritePtr (pLogon, sizeof(CXPLogon)) ||
IsBadReadPtr (pLogon, sizeof(CXPLogon)))
{
TraceMessage ("UploadTimerProc: We got a bogus logon object");
return;
}
pLogon->m_uTimerID = 0;

// If the transport's outgoing logic has been activated in this session,
// send all the deferred messages, by flushing the transport.
if (pLogon->GetTransportStatusCode() & STATUS_OUTBOUND_ENABLED)
{
pLogon->AddStatusBits (UPLOADING_MESSAGES);
}
// If the transport's incoming logic has been activated in this session,
// get any pending messages that need to be placed in the default inbox,
// and set the transport state, ready to get the headers from the server..
if (pLogon->GetTransportStatusCode() & STATUS_INBOUND_ENABLED)
{
pLogon->AddStatusBits (DOWNLOADING_MESSAGES);
pLogon->SetTransportState (PROCESSING_TIMER_EVENT);
}
// Modify the status row now
pLogon->UpdateStatus();
// Reset the time for the next upload. Uploading time is a recurring event.
pLogon->StartUploadTimer();
}

// End of file for XPLOGON.CPP