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