SMBAGENT.C

// --smbagent.c------------------------------------------------------------------- 
//
// This contains the main entry point, initialization and shutdown code, and
// the message processing loop.
//
// Copyright (C) Microsoft Corp. 1986-1996. All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include "smbdata.h"
#include "smbagent.chk"

#include "smbagent.h"
#include "resource.h"

#define STOP_WAIT_HINT 20000 // 20 Second wait before considering the service dead.
#define CFG_POLL_MSEC20000 // 20 Second wait between configuration polls
#define MAX_REG_STR 800

#define TOPIC_CACHE_SIZE10
#define TOPIC_CACHE_DROP_ZONE5

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

// Defined for NT service shell
TCHAR szAppName[] = TEXT("SMBAgent");
TCHAR szWindowTitle[] = TEXT("Sample Mailbox Agent");
TCHAR szServiceName[] = TEXT("SMBAgent");

#ifdef __cplusplus
}

#endif // __cplusplus

static HANDLE hServiceStopEvent = NULL;

//Global variables
HINSTANCE hInst = NULL;
LPMAPISESSION lphSession = NULL; // This is a COPY do not release it!
LPMAPIFOLDER lpStoreFolder = NULL; // Pointer to MDB root folder
LPMAPITABLE lpRootTable = NULL; // Pointer to IPM_SUBTREE hierarchy table
LPMAPIFOLDER lpRootFolder = NULL; // Pointer to IPM_SUBTREE folder
LPMAPIFOLDER lpInFolder = NULL; // Pointer to Inbox folder
LPMAPIFOLDER lpNDRFolder = NULL; // Pointer to the Undeliverable folder
LPMAPIFOLDER lpTopicsFolder = NULL; // Pointer to the Topics folder
LPADRBOOK lpAdrBook = NULL; // Pointer to address book
LPMDB lpStore = NULL; // Pointer to Private Store MDB.
LPMDB lpPubStoreMDB = NULL; // Pointer to Public Store MDB.
LPMAPIFOLDER lpPubStoreFolders = NULL; // Pointer to root of Public Folders.

TCHAR szTextBuf[MAXTEXTLEN * 2] = TEXT(""); // Make these twice as big because sometimes
TCHAR szSubjBuf[MAXSUBJLEN * 2] = TEXT(""); // we concat them with two pieces of data
TCHAR szCmdBuf[MAXCMDLEN * 2] = TEXT(""); // that can be the MAX size.

LPSPropValue lpSenderProps; // Pointer to array of sender properties.

ULONGcbSMBAgentEID;// Session identifiers
LPENTRYIDlpSMBAgentEID;

// These will be updated through the CfgAdviseObject.
LPSPropValue lpCfgProps = NULL; // Ptr to array of cfg ext data properties.
LPTSTR lpszTopicRootFolderName = TOPIC_ROOT_FOLDER_NAME;
LPTSTR lpszTopicRootFolderComment = TOPIC_ROOT_FOLDER_COMMENT;
BOOL bPublicTopic = FALSE;
DWORD dwPollInboxMsec = 60000 * 5; // Default to 5 minutes before polling the inbox.
DWORD dwACLRights = rightsReadOnly;

static TCHAR INQUEUE_NAME[] = TEXT( "Inbox");
static TCHAR OUTQUEUE_NAME[] = TEXT( "Outbox");
static TCHAR INQUEUE_MUTEX[] = TEXT( "InQueueLock");
static TCHAR EXT_DATA_NAME[] = TEXT( "SMBAgent");

static LPCTSTR szInMutexName = INQUEUE_MUTEX;// incoming queue mutex name
static BOOL fIsInit = FALSE;
static HANDLE hChkInBoxEvent = NULL; // Signals notification of new mail.
static HANDLE hInMutex = NULL; // Signals inbox is NOT in use by
// another instance of SMBAGENT.
static ULONG ulInConnection = MAX_ULONG;

static BOOL fInitializedMAPI= FALSE; // MAPI Initialized

static BOOL IsMAPILogon = FALSE; // logged onto MAPI
static LPADVISEOBJ lpCfgAdviseObj = NULL; // Ptr to advise obj for cfg extension data.

static void RegNotifyThread( IN void* UnUsed);

//$--InBoxNotification---------------------------------------------------------
// This is an event handling procedure, called whenever new message(s) arrive
// in the inbox.
// ----------------------------------------------------------------------------
static SCODE STDAPICALLTYPE InBoxNotification(
IN LPVOID lpvContext, //pointer to context
IN ULONG cNotification, //count of notification
IN LPNOTIFICATION lpNotifications) //pointer to notifications
{
HRESULT hr = NOERROR;
if( SetEvent( hChkInBoxEvent) == FALSE)
hr = HR_LOG( E_FAIL);
RETURN( hr);
}

//$--HrProcessInBoxMessages----------------------------------------------------
// Process all messages in the Inbox until a shut down event is signaled.
// ----------------------------------------------------------------------------
static HRESULT HrProcessInBoxMessages()
{
HRESULT hr = NOERROR;
BOOL bRc = TRUE;
SCODE sc = SUCCESS_SUCCESS;
unsigned iMsgIndx = 0;
ULONG ulNMsgTotal = 0;
ULONG ulObjType = 0;
LPMESSAGE lpMessage = NULL;
ULONG cbEntryID = 0;
LPENTRYID lpEntryID = NULL;
LPTSTR lpszCommand = NULL;
DWORD dwRc = 0;
ENTRYLIST elMsgID = {1, NULL};
BOOL fDeleted = FALSE; // TRUE if curr.msg.has been deleted
LPSRowSet lpInRowSet = NULL; // pointer to inbox rows
LPMAPITABLE lpInBoxTable = NULL;

HANDLE hEventArray1[] =
{
hChkInBoxEvent, // Signaled indicates that we recevied notification of new mail.
hServiceStopEvent, // Signaled indicates we are suposed to shutdown.
};

HANDLE hEventArray2[] =
{
hInMutex, // Signaled indicates no other SMBAGENT process is using the inbox,
hServiceStopEvent, // Signaled indicates we are suposed to shutdown.
};

// Request the table be sorted by submittal time.
static const SizedSSortOrderSet(1L,sSortPrioSet) =
{ 1L, 0L, 0L, { PR_CLIENT_SUBMIT_TIME, TABLE_SORT_ASCEND}};

static const SizedSPropTagArray(2L,sPropColumns) =
{ 2L, {PR_ENTRYID, PR_PRIORITY}};

DEBUGPUBLIC( "HrProcessInBoxMessages()");

// Get the contents table for the inbox folder.
ASSERTERROR( lpInFolder != NULL, "NULL lpInFolder!");
hr = MAPICALL(lpInFolder)->GetContentsTable( lpInFolder,
MAPI_DEFERRED_ERRORS, &lpInBoxTable);
if( FAILED( hr))
goto cleanup;

// Table operations need only be performed once
hr = MAPICALL( lpInBoxTable)->SetColumns( lpInBoxTable,
(LPSPropTagArray)&sPropColumns, TBL_BATCH);
if( FAILED( hr))
goto cleanup;

hr = MAPICALL( lpInBoxTable)->SortTable( lpInBoxTable,
(LPSSortOrderSet)&sSortPrioSet, TBL_BATCH);
if( FAILED( hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We go to sleep at the top of this while loop to wait for either the
// hServiceStopEvent to be signaled or both the hChkInBoxEvent and the hInMutex
// to be signaled.
//
// The amount of time we sleep is a SMBADMIN configuration setting. If we time
// out of the sleep then we check the in box for messages. Sometimes the
// notifications are not timely enough so this gives the user the option of
// setting this parameter to INFINITE or some value that makes sense for their
// system setup.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

while( TRUE)
{
// Wait for the Check In Box event or the Shutdown event to be signaled.
dwRc = WaitForMultipleObjects( ARRAY_CNT( hEventArray1), hEventArray1,
FALSE, dwPollInboxMsec);
switch( dwRc)
{
case WAIT_TIMEOUT:
case WAIT_OBJECT_0:
break; // We are supposed to check the inbox.
case WAIT_OBJECT_0 + 1:
goto cleanup; // We are supposed to shutdown.
default:
HR_LOG( E_FAIL);
goto cleanup;
}

// Get ownership of InMutex to prevent another SMBAGENT instance from
// accessing inbox. This only works if both are running on the same
// computer. (This is not recommended.)
dwRc = WaitForMultipleObjects( ARRAY_CNT( hEventArray2), hEventArray2,
FALSE, INFINITE);
switch( dwRc)
{
case WAIT_OBJECT_0:
break; // The inbox is available.
case WAIT_OBJECT_0 + 1:
goto cleanup; // We are supposed to shutdown.
default:
HR_LOG( E_FAIL);
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// At this point we have received notification of new mail or we are just polling
// to check the inbox for mail and we know no other SMBAGENT process on this machine
// is using the inbox.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

DEBUGACTION( "Checking inbox...");

// Resetting InboxEvent will cause us to sleep once we reach the top of
// the loop unless more mail comes in while we are processing.
ResetEvent( hChkInBoxEvent);

// Seek to the beginning of the inbox table.
hr = MAPICALL( lpInBoxTable)->SeekRow( lpInBoxTable,
BOOKMARK_BEGINNING, 0, NULL);
if( FAILED( hr))
goto cleanup;

// Get the entire list of messages that are currently in the inbox.
// More messages may come in while we are processing this list but they
// will not show up for us until we get the list next time.
hr = HrQueryAllRows( lpInBoxTable, NULL, NULL, NULL, 0, &lpInRowSet);
if( FAILED( hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Main loop that actualy processes the inbox messages until end of the table
// list is reached.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

for( iMsgIndx = 0; iMsgIndx < lpInRowSet->cRows; ++iMsgIndx)
{
// Check to make sure we are not supposed to shutdown.
dwRc = WaitForSingleObject( hServiceStopEvent, 0);
if( dwRc == WAIT_OBJECT_0)
break; // Time to shut down.
if( dwRc != WAIT_TIMEOUT)
{
HR_LOG( E_FAIL);
goto cleanup;
}

// Check to see if user paused the service.
hr = HrServiceProcessControl();
if( FAILED( hr))
goto cleanup;

// Initialize the ENTRYLIST with the ENTRYID of the message
elMsgID.lpbin = &(lpInRowSet->aRow[iMsgIndx].lpProps[0].Value.bin);

// Open the next message in the inbox.
cbEntryID = lpInRowSet->aRow[iMsgIndx].lpProps[0].Value.bin.cb;
lpEntryID = (LPENTRYID) (lpInRowSet->aRow[iMsgIndx].lpProps[0].Value.bin.lpb);

// Message has not been deleted since it has not been opened yet.
fDeleted = FALSE;

// Open the message for modification.
hr = MAPICALL(lpInFolder)->OpenEntry( lpInFolder,
cbEntryID, lpEntryID, NULL,
MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
&ulObjType, (LPUNKNOWN FAR *) &lpMessage);
if( SUCCEEDED( hr) && ulObjType == MAPI_MESSAGE)
{ // Process the message.
ASSERTERROR( lpMessage != NULL, "NULL lpMessage!");
hr = MAPICALL(lpMessage)->SetReadFlag(lpMessage,
MAPI_DEFERRED_ERRORS);
if( FAILED( hr))
goto cleanup;

hr = HrProcessCommand( lpMessage, &elMsgID, &fDeleted);
if( hr == MAPI_E_NETWORK_ERROR)
goto cleanup;
}

// If not deleted during processing, delete the message from inbox
if( lpMessage && !fDeleted)
{
hr = MAPICALL(lpInFolder)->DeleteMessages( lpInFolder,
&elMsgID, 0, NULL, 0);
if( FAILED( hr))
goto cleanup;
}

ULRELEASE( lpMessage); // Free the message pointer
lpMessage = NULL;
}
// Free the current row set of message structures.
FREEPROWS( lpInRowSet);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We are done with all messages in the table, there may be more in the inbox,
// but not in this table. So we release the table and prepare to go back to
// sleep. If more messages are waiting in the inbox we will have received a
// signal and will continue immediately.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Unlock the inbox.
if( ReleaseMutex( hInMutex) == FALSE)
{
HR_LOG( E_FAIL);
goto cleanup;
}

}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We are supposed to shutdown.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cleanup:

// Unlock the inbox. This can fail if we don't have the inbox locked.
// So we don't care about the return results in this case.
ReleaseMutex( hInMutex);

ULRELEASE( lpInBoxTable);

RETURN( hr);
}

//$--HrOpenHierarchicalFolder---------------------------------------------------
// Finds and opens hierarchical folder. Creates folders as necessary.
// -----------------------------------------------------------------------------
static HRESULT HrOpenHierarchicalFolder(
IN LPMAPIFOLDER lpParentFolder, // pointer to parent folder
IN LPTSTR lpszFolderPath, // Folder name
IN LPTSTR lpszDefaultComment, // Folder comment
OUT LPMAPIFOLDER *lppFolder) // pointer to open folder
{
HRESULT hr = NOERROR;
LPMAPIFOLDER lpFolder = NULL;
LPTSTR *lppszFolderList = NULL;
ULONG ulFolderCount = 0L;
ULONG i = 0L;

DEBUGPUBLIC( "HrOpenHierarchicalFolder()");

hr = CHK_HrOpenHierarchicalFolder(
lpParentFolder, lpszFolderPath, lpszDefaultComment,
lppFolder);
if( FAILED( hr))
RETURN( hr);

*lppFolder = NULL;

hr = HrStrTokAll( lpszFolderPath, TEXT("\\"), &ulFolderCount, &lppszFolderList);
if( FAILED( hr))
goto cleanup;

// Attempt to find the topics folder.
for( i=0; i<ulFolderCount; i++)
{
hr = MAPICALL(lpParentFolder)->CreateFolder( lpParentFolder,
FOLDER_GENERIC,
lppszFolderList[i],
lpszDefaultComment,
NULL,
fMapiUnicode | OPEN_IF_EXISTS | MAPI_DEFERRED_ERRORS,
&lpFolder);
if( FAILED( hr))
goto cleanup;

ASSERT_IUNKNOWN_PTR( lpFolder, "INVALID lpFolder pointer");

// No longer need parent folder.
// (Don't release folder that was passed!)
if( i > 0L)
ULRELEASE( lpParentFolder);

lpParentFolder = lpFolder;
lpFolder = NULL;
}

// Success!
*lppFolder = lpParentFolder;

cleanup:
MAPIFREEBUFFER( lppszFolderList);

if( FAILED( hr))
ULRELEASE( lpFolder);

RETURN( hr);
}

//$--HrInitTopicFolders--------------------------------------------------------
// Initialize Topic folder interfaces and cache
// ----------------------------------------------------------------------------
HRESULT HrInitTopicFolders( VOID)
{
HRESULT hr = NOERROR;
LPMAPIFOLDER lpParentFolder = NULL;

DEBUGPRIVATE( "HrInitTopicFolders()");

if( !lpTopicsFolder)
{
if( bPublicTopic)
lpParentFolder = lpPubStoreFolders;
else
lpParentFolder = lpRootFolder;

hr = HrOpenHierarchicalFolder(
lpParentFolder, lpszTopicRootFolderName, lpszTopicRootFolderComment, &lpTopicsFolder);
if( FAILED(hr))
goto cleanup;

if( bPublicTopic)
{
hr = HrModifyACL(
lpTopicsFolder,
TEXT("Default"),
0L,
NULL,
FALSE,
rightsReadOnly);
if( FAILED( hr))
goto cleanup;
}

// Reinitialize the topic folder array and topic cache
hr = STFolderArray_HrInit();
if( FAILED( hr))
goto cleanup;

hr = STopicCache_HrInit( TOPIC_CACHE_SIZE, TOPIC_CACHE_DROP_ZONE);
if( FAILED( hr))
goto cleanup;
}

cleanup:
if( FAILED( hr))
ULRELEASE( lpTopicsFolder);

RETURN( hr);
}

//$--HrUninitTopicFolders------------------------------------------------------
// Release folder interfaces
// ----------------------------------------------------------------------------
static VOID UninitTopicFolders( VOID)
{
STopicCache_ReleaseAll();
STFolderArray_Destroy();

ULRELEASE( lpTopicsFolder);

}

//$--HrCfgChanged()-------------------------------------------------------------
// This function gets called once when the advise is initialized and then when
// the configuration extension data changes.
//
// This data is maintained by a property sheet found in Exchange's Administration
// program for the mailbox that we have logged onto. The SMBAdmin.DLL supports
// the property sheet.
// -----------------------------------------------------------------------------
static HRESULT HrCfgChanged(
LPVOID lpvUserContext, // Not used.
LPWSTR lpwszBlobName, // Not used.
ULONG cProps, // Count of propetries in lpProps.
LPSPropValue lpProps) // Pointer to the array of properties.
{
HRESULT hr = NOERROR;
LPMAPIFOLDER lpParentFolder = NULL;
DWORD dwRc = 0;

HANDLE hEventArray[] =
{
hInMutex, // Signaled indicates no other SMBAGENT process is using the inbox,
hServiceStopEvent, // Signaled indicates we are suposed to shutdown.
};


DEBUGPUBLIC( "HrCfgChanged()");

// Return a special error if there is no extension data. This is an
// expected condition, but we must log why we don't continue. This
// MUST be done before the CHK_ function is called.
if( cProps != SMBDATA_PROP_COUNT)
{
EventLogMsg( EDKEVENT_ERROR,
1, "Extension data ("SMBBLOBNAME") corrupt or non-existent. Install and run SMBAdmin.",
0);
RETURN( EDK_E_NOT_FOUND);
}

// This makes sure we have exactly the expected number of properties.
hr = CHK_HrCfgChanged( lpvUserContext, lpwszBlobName, cProps, lpProps);
if( FAILED( hr))
RETURN( hr);


// Get ownership of InMutex so that we don't interrupt work in progress
dwRc = WaitForMultipleObjects( ARRAY_CNT( hEventArray), hEventArray, FALSE, INFINITE);
switch( dwRc)
{
case WAIT_OBJECT_0: // The inbox is available.
break;

case WAIT_OBJECT_0 + 1: // We are supposed to shutdown.
goto cleanup;

case WAIT_ABANDONED_0: // mutex abandoned?
MODULE_ERROR( "Mutex abandoned! You may need to reboot.");
/* FALL-THROUGH */

default:
hr = HR_LOG( E_FAIL);
goto cleanup;
}

// Release old topic folder information
UninitTopicFolders();

// Extract data from property array. Note that we keep the property array
// around until next time a change occurs or termination.
lpszTopicRootFolderName = lpProps[ IDX_TOPIC_ROOT_FOLDER_NAME ].Value.LPSZ;
lpszTopicRootFolderComment = lpProps[ IDX_TOPIC_ROOT_FOLDER_COMMENT].Value.LPSZ;
dwPollInboxMsec = lpProps[ IDX_POLL_INBOX_MSEC ].Value.ul;
bPublicTopic = lpProps[ IDX_PUBLIC_TOPIC_FOLDER ].Value.b;
dwACLRights = lpProps[ IDX_ACL_RIGHTS ].Value.ul;

// Free the previous set of properties and remember the new one.
MAPIFREEBUFFER( lpCfgProps);
lpCfgProps = lpProps;

// NOTE: Topic folder is initialized when processing mail
// so error message can be returned if initialization fails.

if( ReleaseMutex( hInMutex) == FALSE)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}

cleanup:
RETURN( hr);
}

//$--UninitSMBAGENT ----------------------------------------------------------
// Uninitialize the service
// ----------------------------------------------------------------------------
static VOID UninitSMBAGENT( VOID)
{
// Release all data cached in the topic cache and destroy the folder array.
UninitTopicFolders();

// Stop configuration extension data notification.
if( lpCfgAdviseObj)
{
HR_LOG( HrCfgDestroyAdviseObj( lpCfgAdviseObj));
lpCfgAdviseObj = NULL;
}
MAPIFREEBUFFER( lpCfgProps);

// Stop inbox notifications.
if((ulInConnection != MAX_ULONG) && (lpStore != NULL))
MAPICALL(lpStore)->Unadvise(lpStore, ulInConnection);

CLOSEHANDLE( hChkInBoxEvent);
CLOSEHANDLE( hInMutex);

// Release global object pointers.
ULRELEASE( lpAdrBook);
ULRELEASE( lpNDRFolder);
ULRELEASE( lpInFolder);
ULRELEASE( lpRootTable);
ULRELEASE( lpRootFolder);
ULRELEASE( lpStoreFolder);

// Release the message stores
ULRELEASE(lpStore);

ULRELEASE( lpPubStoreFolders);
ULRELEASE( lpPubStoreMDB);

MAPIFREEBUFFER( lpSMBAgentEID);

// Logoff a MAPI session
if((IsMAPILogon == TRUE) && (lphSession != NULL))
{
MAPICALL( lphSession)->Logoff( lphSession, 0, 0, 0);
ULRELEASE( lphSession);
IsMAPILogon = FALSE;
}

if(fInitializedMAPI == TRUE)
{
MAPIUninitialize();
fInitializedMAPI = FALSE;
}

fIsInit = FALSE;
}

//$--InitSMBAGENT ----------------------------------------------------------------
// Initialize for receiving & sending messages
// -----------------------------------------------------------------------------
static HRESULT HrInitSMBAGENT( void) // RETURNS: return code
{
HRESULT hr = NOERROR;
SCODE sc = SUCCESS_SUCCESS;
LPTSTR * lppszArgv = NULL;
LPTSTR lpszPassword = TEXT("");
ULONG dwArgc = 0, // number of arguments to service
cbInEntryID = 0, // #bytes in Inbox EntryID
cbRootEntryID = 0, // #bytes in Root Folder EntryID
cbNDREntryID = 0, // #bytes in Undeliverable Folder EntryID
ulObjType = 0,
ulFlags = 0;

LPENTRYID lpInEntryID = NULL, // Pointer to Inbox folder EntryID
lpRootEntryID = NULL, // Pointer to Root folder EntryID
lpNDREntryID = NULL; // Pointer to Undeliverable Entry ID
MAPIINIT_0 MapiInit;

ULONG cbeid = 0;
LPENTRYID lpeid = NULL;
LPMAPIADVISESINK lpInAdvise = NULL;

TCHAR szServiceName[MAX_SERVICE_NAME_LENGTH+1] = {TEXT("")};

TCHAR szProfileName[MAX_PATH+1] = {0};

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize MAPI, create temporary profile, and logon
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if( !FIsService())
hr = MAPIInitialize(NULL); // Not a service.
else
{ // Services need special initialization.
MapiInit.ulVersion = MAPI_INIT_VERSION;
MapiInit.ulFlags = MAPI_NT_SERVICE;
ulFlags = MAPI_NT_SERVICE | MAPI_NO_MAIL;
hr = MAPIInitialize( &MapiInit);
}
if( FAILED( hr))
goto cleanup;

fInitializedMAPI = TRUE;

hr = HrServiceGetArgv(&dwArgc, &lppszArgv);
if( FAILED( hr))
goto cleanup;

ASSERTERROR( dwArgc != 0L, "Zero dwArgc");
ASSERTERROR( lppszArgv != NULL, "NULL lpszArgv");

if( dwArgc > 1)
lpszPassword = lppszArgv[1];

hr = HrServiceGetName( szServiceName);
if(FAILED( hr))
goto cleanup;

hr = HrCreateProfileName( szServiceName, MAX_PATH+1, szProfileName);
if(FAILED(hr))
goto cleanup;

hr = HrCreateMailboxAgentProfile( szServiceName, szProfileName);

if(hr == E_ACCESSDENIED)
{
MODULE_WARNING("Can't create profile--it already exists.");
hr = NOERROR;
}
else if(FAILED(hr))
{
goto cleanup;
}

hr = MAPILogonEx(
(ULONG) 0,
szProfileName,
lpszPassword,
MAPI_NEW_SESSION | MAPI_EXTENDED | ulFlags,
&lphSession);

if( FAILED( hr))
goto cleanup;

ASSERTERROR( lphSession!=NULL, "Null lphSession!");

IsMAPILogon = TRUE;

// Mark profile for later deletion
HR_LOG(HrRemoveProfile(szProfileName));

// Make a note of who we are. This will be used later to tell if messages are
// sent from the SMBAgent profile. (Something only a tester would do...)
hr = MAPICALL(lphSession)->QueryIdentity (lphSession, &cbSMBAgentEID, &lpSMBAgentEID);
if (FAILED(hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Misc. folder finding and opening including the InBox.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Open the public store MDB.
hr = HrOpenExchangePublicStore(lphSession, &lpPubStoreMDB);
if( FAILED(hr))
RETURN( hr);

hr = HrOpenExchangePublicFolders( lpPubStoreMDB, &lpPubStoreFolders);
if( FAILED( hr))
goto cleanup;

// Get entry ID of message store
hr = HrMAPIFindDefaultMsgStore( lphSession, &cbeid, &lpeid);
if( FAILED(hr))
goto cleanup;

hr = MAPICALL( lphSession)->OpenMsgStore( lphSession,
(ULONG)0,
cbeid,
lpeid,
NULL,
MDB_NO_DIALOG | MDB_WRITE,
&lpStore);

MAPIFREEBUFFER(lpeid);

if( FAILED( hr))
goto cleanup;

// Get the root folder in the default message store
hr = MAPICALL(lpStore)->OpenEntry( lpStore,
(ULONG)0,
NULL,
NULL,
MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN FAR *) &lpStoreFolder);
if( FAILED( hr))
goto cleanup;

// Find Message Store's IPM folder subtree
hr = HrMAPIFindIPMSubtree( lpStore, &cbRootEntryID, &lpRootEntryID);
if( FAILED(hr))
goto cleanup;

ASSERTERROR( cbRootEntryID != 0, "ZERO cbRootEntryID!");
ASSERTERROR( lpRootEntryID != NULL, "NULL lpRootEntryID!");

// Open SMBAGENT's IPM_SUBTREE folder
hr = MAPICALL(lpStoreFolder)->OpenEntry( lpStoreFolder,
cbRootEntryID,
lpRootEntryID,
NULL,
MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN FAR *) &lpRootFolder);
if( FAILED(hr))
goto cleanup;
if( ulObjType != MAPI_FOLDER)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}

ASSERTERROR( lpRootFolder != NULL, "NULL lpRootFolder!");

// Get the hierarchy table for the root folder of the message store
hr = MAPICALL(lpRootFolder)->GetHierarchyTable( lpRootFolder,
MAPI_DEFERRED_ERRORS,
&lpRootTable);
if( FAILED( hr))
goto cleanup;

// Find SMBAGENT's Inbox folder
hr = HrMAPIFindInbox( lpStore, &cbInEntryID, &lpInEntryID);
if( FAILED( hr))
goto cleanup;

ASSERTERROR ( lpInEntryID != NULL, "NULL lpInEntryID");

// Open SMBAGENT's Inbox folder
hr = MAPICALL(lpRootFolder)->OpenEntry( lpRootFolder,
cbInEntryID,
lpInEntryID,
NULL,
MAPI_MODIFY|MAPI_DEFERRED_ERRORS,
&ulObjType,
(LPUNKNOWN FAR *) &lpInFolder);
if( FAILED(hr))
goto cleanup;
if( ulObjType != MAPI_FOLDER)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}

ASSERTERROR( lpInFolder != NULL, "NULL lpInFolder!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Open the "Undeliverable" folder, create it if necessary.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


hr = HrOpenHierarchicalFolder( lpRootFolder, NDR_FOLDER_NAME, NDR_FOLDER_COMMENT, &lpNDRFolder);
if( FAILED(hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Open the Address Book.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

hr = MAPICALL(lphSession)->OpenAddressBook( lphSession, 0L, NULL, AB_NO_DIALOG, &lpAdrBook);
if( FAILED( hr))
goto cleanup;

ASSERTERROR( lpAdrBook != NULL, "NULL lpAdrBook!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Support processing the in box messages using notification and event signals.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Create Inbox mutex (w/o ownership) for possible multiple accesses
hInMutex = CreateMutex( NULL, FALSE, szInMutexName);
if(hInMutex == NULL)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}

// Create an event for signaling to check the inbox for messages to process.
// This is a manual reset event that is initialy signaled.
hChkInBoxEvent = CreateEvent( NULL, TRUE, TRUE, NULL);
if( hChkInBoxEvent == NULL)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}

// Register incoming queue event notification handler.
hr = HrAllocAdviseSink( InBoxNotification, NULL, &lpInAdvise);
if( FAILED( hr))
goto cleanup;
hr = MAPICALL(lpStore)->Advise( lpStore, cbInEntryID, lpInEntryID,
fnevNewMail, lpInAdvise, &ulInConnection);
if( FAILED( hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Start configuration change notification engine. This will cause the
// HrCfgChanged() function to be called immediately and then every time a change
// occurs. The configuration data is stored by Exchange as extension data and
// it is maintained by SMBAdmin.DLL.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

hr = HrCfgCreateAdviseObj( lphSession, CFG_POLL_MSEC, &lpCfgAdviseObj);
if( FAILED( hr))
goto cleanup;

hr = HrCfgAdvise( lpCfgAdviseObj, SMBBLOBNAME, &HrCfgChanged, NULL);
if( FAILED( hr))
goto cleanup;


// HrCfgChanged will open the appropriate Topics Folder and
// initialize the topic cache
ASSERTERROR( lpTopicsFolder != NULL, "NULL lpTopicsFolder!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

fIsInit = TRUE;

cleanup:

ULRELEASE(lpInAdvise);

MAPIFREEBUFFER(lpeid);
MAPIFREEBUFFER(lpInEntryID);
MAPIFREEBUFFER(lpRootEntryID);
MAPIFREEBUFFER(lpNDREntryID);

if( FAILED( hr))
{
UninitSMBAGENT();

SetServiceExitCode( ERROR_INTERNAL_ERROR, hr);

ServiceStop();
}

RETURN(hr);
}

//$--HrServiceStartup----------------------------------------------------------
// This function is called at startup to initialize the application.
//------------------------------------------------------------------------------
HRESULT HrServiceStartup(
IN HINSTANCE hInstance, // Handle of current instance
IN HINSTANCE hPrevInstance, // Handle of previous instance
IN HWND hwndMainWindow, // Handle to Main window
IN LPSTR pszCmdLine) // Pointer to command line
{
HRESULT hr = NOERROR;

DEBUGPUBLIC( "fNTServiceStartup()");

hr = CHK_HrServiceStartup( hInstance, hPrevInstance, hwndMainWindow, pszCmdLine);
if( FAILED( hr))
RETURN( hr);

// Save the instance value as a global variable
hInst = hInstance;

hServiceStopEvent = GetServiceStopEvent();

hr = HR_LOG(HrEventOpenLog(
TEXT("SMBAGENT"), NULL, TEXT("EDKMSG.DLL"), NULL, NULL, NULL));

// Start message transfer.
RETURN( HrInitSMBAGENT());
}

//$--ServiceMain--------------------------------------------------------------
// This function takes care of some service overhead and starts in box message
// processing.
//
// This is a thread process and keeps running until it is time to shut down.
//------------------------------------------------------------------------------
void ServiceMain(
IN HANDLE NotUsed) // Handle to Shutdown event object
{
HRESULT hr = NOERROR;
DWORD dwRc = 0;

DEBUGPUBLIC( "ServiceMain()");
hr = CHK_ServiceMain( NotUsed);
if( FAILED( hr))
goto cleanup;

EventLogMsg( EDKEVENT_INFORMATION, 1, "Starting ServiceMain", 0);

// We have a loop for processing messages so that
// we can recover when the server goes down.
while( TRUE)
{
// Check to see if user paused the service.
hr = HrServiceProcessControl();
if( FAILED( hr))
goto cleanup;

hr = HrProcessInBoxMessages();
if( hr == MAPI_E_NETWORK_ERROR)
{ // The server went down so we reset the program and wait for it to come back up.
EventLogMsg( EDKEVENT_INFORMATION, 1, "Mailbox server is down. Attempting to reconnect.", 0);

UninitSMBAGENT();

while( TRUE)
{
hr = HrInitSMBAGENT();
if( SUCCEEDED( hr))
break;

// Wait one minute or until administrator shuts us down.
dwRc = WaitForSingleObject( hServiceStopEvent, 10000);
switch( dwRc)
{
case WAIT_TIMEOUT:
break; // We are supposed to check the inbox.
case WAIT_OBJECT_0 + 1:
goto cleanup; // We are supposed to shutdown.
default:
HR_LOG( E_FAIL);
goto cleanup;
}
}

EventLogMsg( EDKEVENT_INFORMATION, 1, "Mailbox server came back up.", 0);
continue;
}

// Exit loop after testing for error condition.
if( FAILED( hr))
goto cleanup;
break;
}

cleanup:
// If an error occured we need to stop the service ourselves.
if( FAILED( hr))
{
SetServiceExitCode( ERROR_INTERNAL_ERROR, hr);

ServiceStop();
}

// Call this function to let winwrap know we are done.
HrServiceConfirmStop();

EventLogMsg( EDKEVENT_INFORMATION, 1, "Ending ServiceMain", 0);
ExitThread( hr);
}

//$--HrServiceShutdown----------------------------------------------------------
// This function is called to shutdown the application.
//------------------------------------------------------------------------------
HRESULT HrServiceShutdown (void) // RETURNS: Return value for WinMain
{
// Uninitialize...
UninitSMBAGENT();

(void) HrEventCloseLog();

// Return exit code for WinMain...
return( NOERROR);
}

//------------------------------------------------------------------------------