SMH.C
/* 
 *  S M H . C 
 *   
 *  Sample mail handling hook 
 *   
 *  Purpose: 
 *   
 *      The sample mail handling (SMH) hook illustrates the use of the 
 *      MAPI Spooler Hook Provider Interface (ISpoolerHook) and those 
 *      parts of the general MAPI API that are available to a spooler 
 *      hook implementation. 
 *   
 *      Specifically, SMH illusttrates the operation of both an inbound 
 *      message hook as well as outbound.  SMH also has examples of the 
 *      configuration of a spooler hook provider via calls to SMH's 
 *      ServiceEntry() and/or through calls from the Profile Wizard. 
 *   
 *  Features: 
 *   
 *    Sent Mail: 
 *   
 *      SMH allows the archiving of outbound messages (sent mail), into a 
 *      well defined set of subfolders in the default stores sent mail 
 *      folder.  The archive folders are comprised of monthly archive 
 *      folders.  The monthly folders can be, optionally, created under a 
 *      year based folder in the sent mail folder.  Thus in a typical 
 *      message store, a fully archived sent mail folder might have a 
 *      hierarchy that looks similar to the following: 
 *   
 *          Sent Mail 
 *              | 
 *              |-- 1994 
 *              |    | 
 *              |    |-- 10 October 
 *              |    |-- 11 November 
 *              |    |-- 12 December 
 *              | 
 *              |-- 1995 
 *                   | 
 *                   |-- 01 January 
 *   
 *      This allows for a mail user to organize their outgoing mail in 
 *      a managible fashion. 
 *   
 *    Deleted Mail: 
 *   
 *      SMH allows the archiving of deleted mail in the same fashion as 
 *      sent mail can be archived.  This feature helps people who choose 
 *      keep their 'deleted' mail accessible.  It should be noted here 
 *      that this feature does not make use of the ISpoolerHook 
 *      interface, but is an example of what all can be done with spooler 
 *      hook providers. 
 *   
 *    Incoming Mail: 
 *   
 *      SMH can also 'filter' incoming messages and route the message 
 *      directly to folders, other than the default stores inbox, based 
 *      on message content.  The user can define any number of filters 
 *      that can help organize and manage the email.  Additionally, the 
 *      user can specify a sound scheme to be used on message delivery. 
 *      This allows the user to specify specific sounds based on message 
 *      content and priority. 
 *   
 *    Unread Search Folders: 
 *   
 *      Because SMH can filter unread messages deep into a message store 
 *      folder hierarchy, SMH can also create a search folder in the root 
 *      of the IPM_SUBTREE that searches the entire subtree for unread 
 *      messages. 
 *   
 *    Out-of-office Processing: 
 *   
 *      SMH can generate OOF notifications for incomming mail.  It will 
 *      do it's best to not send multiple OOF's to any single recipient 
 *      over the period in which the OOF processing is enabled.  The OOF 
 *      engine supports full rich-text. 
 *   
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved. 
 */ 
 
#include "_pch.h" 
 
extern SPropTagArray sptRule; 
extern SPropTagArray sptConfigProps; 
 
/* 
 *  sptMessageProps 
 * 
 *  These are the properties that are required to check filters against 
 *  messages. 
 */ 
const static SizedSPropTagArray (cpMsgPrps, sptMsgPrps) = 
{ 
    cpMsgPrps, 
    { 
        PR_MESSAGE_FLAGS, 
        PR_SUBJECT, 
        PR_SENT_REPRESENTING_NAME, 
        PR_SENT_REPRESENTING_EMAIL_ADDRESS, 
        PR_IMPORTANCE 
    } 
}; 
 
/* 
 *  vtblSMH 
 * 
 *  This is the SMH object's vtable.  The table and its functions are 
 *  defined by MAPI's ISpoolerHook interface. 
 */ 
static const SMH_Vtbl vtblSMH = 
{ 
    SMH_QueryInterface, 
    SMH_AddRef, 
    SMH_Release, 
    SMH_InboundMsgHook, 
    SMH_OutboundMsgHook 
}; 
 
/* 
 *  lpCtl3D 
 *   
 *  Holds context for the 3D control DLL. 
 */ 
 
LPVOID lpCtl3D = NULL; 
 
/* 
 *  HrInitUnreadSearch() 
 * 
 *  Purpose: 
 * 
 *      Inits/creates an 'Unread Messages' search folder in the root of 
 *      the given store's IPM_SUBTREE hierarchy. 
 * 
 *  Arguments: 
 * 
 *      lpsmh           the sample mail handler object 
 *      lpmdb           the store getting the search folder 
 * 
 *  Returns: 
 * 
 *      (HRESULT) 
 */ 
HRESULT 
HrInitUnreadSearch (LPSMH lpsmh) 
{ 
    HRESULT hr; 
    ENTRYLIST el = {0}; 
    LPMAPIFOLDER lpfldr = NULL; 
    LPMAPIFOLDER lpfldrUM = NULL; 
    LPMDB lpmdb = NULL; 
    LPSPropValue lpval = NULL; 
    SRestriction res = {0}; 
    ULONG ulType = 0; 
    UINT cerr = 0; 
    UINT i; 
 
    for (i = 0; i < lpsmh->lpstotbl->cSto; i++) 
    { 
        hr = HrOpenStoEntry (lpsmh->lpsess, &lpsmh->lpstotbl->aSto[i], &lpmdb); 
        if (!HR_FAILED (hr)) 
        { 
            hr = HrGetOneProp ((LPMAPIPROP)lpmdb, PR_IPM_SUBTREE_ENTRYID, &lpval); 
            if (!HR_FAILED (hr)) 
            { 
                hr = lpmdb->lpVtbl->OpenEntry (lpmdb, 
                                        lpval->Value.bin.cb, 
                                        (LPENTRYID)lpval->Value.bin.lpb, 
                                        NULL, 
                                        MAPI_MODIFY, 
                                        &ulType, 
                                        (LPUNKNOWN FAR *)&lpfldr); 
                if (!HR_FAILED (hr)) 
                { 
                    hr = lpfldr->lpVtbl->CreateFolder (lpfldr, 
                                        FOLDER_SEARCH, 
                                        "Unread Messages", 
                                        "Simple Mail Handler: unread message search", 
                                        NULL, 
                                        MAPI_MODIFY | OPEN_IF_EXISTS, 
                                        &lpfldrUM); 
                    if (!HR_FAILED (hr)) 
                    { 
                        el.cValues = 1; 
                        el.lpbin = &lpval->Value.bin; 
                        res.rt = RES_BITMASK; 
                        res.res.resBitMask.relBMR = BMR_EQZ; 
                        res.res.resBitMask.ulPropTag = PR_MESSAGE_FLAGS; 
                        res.res.resBitMask.ulMask = MSGFLAG_READ; 
                        hr = lpfldrUM->lpVtbl->SetSearchCriteria (lpfldrUM, 
                                        &res, 
                                        &el, 
                                        RECURSIVE_SEARCH | BACKGROUND_SEARCH | RESTART_SEARCH); 
                        UlRelease (lpfldrUM); 
                        lpfldrUM = NULL; 
                    } 
                    UlRelease (lpfldr); 
                    lpfldr = NULL; 
                } 
                (*lpsmh->lpfnFree) (lpval); 
                lpval = NULL; 
            } 
        } 
        if (HR_FAILED (hr)) 
        { 
            DebugTrace ("SMH: WARNING: failed to init unread search (store:%d)\n", i); 
            cerr++; 
        } 
    } 
 
    hr = ResultFromScode (cerr ? MAPI_W_ERRORS_RETURNED : S_OK); 
    DebugTraceResult (HrInitUnreadSearch(), hr); 
    return hr; 
} 
 
 
/* 
 *  LpszFindChar() 
 * 
 *  Purpose: 
 * 
 *      Finds the given character in the passed in string.  This is an 
 *      exact matching model so strings should be normalized to either 
 *      upper or lower case if case insensitvity is required. 
 * 
 *  Arguments: 
 * 
 *      lpszSrc         source string 
 *      ch              character to find 
 * 
 *  Returns: 
 * 
 *      NULL iff the char was not found, otherwise, the return is a 
 *      pointer to the character in the source string. 
 */ 
LPTSTR 
LpszFindChar (LPTSTR lpszSrc, TCHAR ch) 
{ 
    LPTSTR lpsz = lpszSrc; 
 
    if (lpszSrc) 
        while (*lpsz && (*lpsz != ch)) 
            lpsz++; 
 
    return ((lpsz && *lpsz) ? lpsz : NULL); 
} 
 
 
/* 
 *  HrCheckExclusions() 
 * 
 *  Purpose: 
 * 
 *      Checks the message class against the list of message classes 
 *      excluded from filtering. 
 * 
 *  Arguments: 
 * 
 *      lpsmh           the smh parent object 
 *      lpmsg           the message to check for exclusion 
 * 
 *  Returns: 
 * 
 *      (HRESULT)       If the message is to be excluded, then hrSuccess 
 *                      is returned, otherwize MAPI_E_NOT_ME indicates it 
 *                      is OK to filter the message 
 */ 
HRESULT 
HrCheckExclusions (LPSMH lpsmh, LPMESSAGE lpmsg) 
{ 
    SCODE sc = S_OK; 
    UINT isz = 0; 
    LPSPropValue lpval = NULL; 
 
    if (lpsmh->valEx.ulPropTag == PR_SMH_EXCLUSIONS) 
    { 
        if (!HR_FAILED (HrGetOneProp ((LPMAPIPROP)lpmsg, PR_MESSAGE_CLASS, &lpval))) 
        { 
            /*  See if this is in the list of exclusions */ 
 
            for (isz = 0; isz < lpsmh->valEx.Value.MVSZ.cValues; isz++) 
                if (FLpszContainsLpsz (lpval->Value.LPSZ, lpsmh->valEx.Value.MVSZ.LPPSZ[isz])) 
                    break; 
 
            (*lpsmh->lpfnFree) (lpval); 
        } 
        sc = ((isz == lpsmh->valEx.Value.MVSZ.cValues) ? S_OK : MAPI_E_NOT_ME); 
    } 
 
    DebugTraceSc (HrCheckExclusions(), sc); 
    return ResultFromScode (sc); 
} 
 
 
/* 
 *  HrCheckRule() 
 *   
 *  Purpose: 
 *   
 *      Examines a message to see if the current rule applies to the 
 *      message.  
 *   
 *      IMPORTANT: If a rule is of type RL_SUBJECT, RL_FROM, or 
 *      RL_ATTACH, then the set of properties required to check for 
 *      matches with the message are retained and passed back to the 
 *      caller such that subsequent calls to HrCheckRule() will not have 
 *      to get those props a second time.  THEREFORE the caller is 
 *      responsible for cleaning up any returned property values. 
 *   
 *  Arguments: 
 *   
 *      lprl            pointer to the rule 
 *      lpmsg           pointer to the message to examine 
 *      lppval  [OUT]   buffer containing the target entryid value struct 
 *   
 *  Returns: 
 *   
 *      (HRESULT)       If the rule does apply, hrSuccess is returned and 
 *                      the lppval parameter will point to a lpval struct 
 *                      containing the entryid of the target folder. 
 *   
 *                      If the rule does not apply, the return value is 
 *                      an HRESULT with MAPI_E_NOT_ME as the SCODE. 
 *   
 *                      Otherwise normal error codes apply. 
 */ 
HRESULT 
HrCheckRule (LPSMH lpsmh, LPRULE lprl, LPMESSAGE lpmsg, LPSPropValue FAR * lppval) 
{ 
    HRESULT hr; 
    LPMAPITABLE lptbl = NULL; 
    LPSPropValue lpval = *lppval; 
    LPSPropValue lpvalT = NULL; 
    ULONG cval; 
 
    if (!lpval) 
    { 
        hr = lpmsg->lpVtbl->GetProps (lpmsg, 
                                (LPSPropTagArray)&sptMsgPrps, 
                                0, 
                                &cval, 
                                &lpval); 
        if (HR_FAILED (hr)) 
            goto ret; 
    } 
 
    /*  Init for failure */ 
 
    hr = ResultFromScode (MAPI_E_NOT_ME); 
 
    if ((lprl->rlTyp == RL_TO_RECIP) || 
        (lprl->rlTyp == RL_CC_RECIP) || 
        (lprl->rlTyp == RL_BCC_RECIP) || 
        (lprl->rlTyp == RL_ANY_RECIP)) 
    { 
        hr = lpmsg->lpVtbl->GetRecipientTable (lpmsg, 0, &lptbl); 
        if (HR_FAILED (hr)) 
            goto ret; 
 
        hr = lptbl->lpVtbl->FindRow (lptbl, lprl->lpres, BOOKMARK_BEGINNING, 0); 
        UlRelease (lptbl); 
        if (HR_FAILED (hr) && (GetScode (hr) == MAPI_E_NOT_FOUND)) 
            hr = ResultFromScode (MAPI_E_NOT_ME); 
    } 
    else if (lprl->rlTyp == RL_SUBJECT) 
    { 
        if (lpval[ipMsgSubj].ulPropTag == PR_SUBJECT) 
            if (FLpszContainsLpsz (lpval[ipMsgSubj].Value.LPSZ, lprl->lpszData)) 
                hr = hrSuccess; 
    } 
    else if (lprl->rlTyp == RL_SENDER) 
    { 
        if (lpval[ipMsgSentRep].ulPropTag == PR_SENT_REPRESENTING_NAME) 
            if (FLpszContainsLpsz (lpval[ipMsgSentRep].Value.LPSZ, lprl->lpszData)) 
                hr = hrSuccess; 
 
        if (HR_FAILED (hr)) 
            if (lpval[ipMsgSentRepEA].ulPropTag == PR_SENT_REPRESENTING_EMAIL_ADDRESS) 
                if (FLpszContainsLpsz (lpval[ipMsgSentRepEA].Value.LPSZ, lprl->lpszData)) 
                    hr = hrSuccess; 
    } 
    else if (lprl->rlTyp == RL_HAS_ATTACH) 
    { 
        if (lpval[ipMsgFlgs].ulPropTag == PR_MESSAGE_FLAGS) 
            if (lpval[ipMsgFlgs].Value.l & MSGFLAG_HASATTACH) 
                hr = hrSuccess; 
    } 
    else if (lprl->rlTyp == RL_BODY) 
    { 
        if (!HR_FAILED (HrGetOneProp ((LPMAPIPROP)lpmsg, PR_BODY, &lpvalT))) 
        { 
            if (FLpszContainsLpsz (lpvalT->Value.LPSZ, lprl->lpszData)) 
                hr = hrSuccess; 
 
            (*lpsmh->lpfnFree) (lpvalT); 
        } 
    } 
    else if (lprl->rlTyp == RL_MSG_CLASS) 
    { 
        if (!HR_FAILED (HrGetOneProp ((LPMAPIPROP)lpmsg, PR_MESSAGE_CLASS, &lpvalT))) 
        { 
            if (FLpszContainsLpsz (lpvalT->Value.LPSZ, lprl->lpszData)) 
                hr = hrSuccess; 
 
            (*lpsmh->lpfnFree) (lpvalT); 
        } 
    } 
 
    if (lprl->ulFlags & RULE_NOT) 
    { 
        if (GetScode (hr) == MAPI_E_NOT_ME) 
            hr = hrSuccess; 
        else if (hr == hrSuccess) 
            hr = ResultFromScode (MAPI_E_NOT_ME); 
    } 
     
ret: 
 
    *lppval = lpval; 
    DebugTraceResult (HrCheckRule(), hr); 
    return hr; 
} 
 
 
/* 
 *  HrFolderFromPath() 
 * 
 *  Purpose: 
 * 
 *      Takes a IPM root-based path string and returns a folder 
 *      corresponding to the path given.  The '\' character is the path 
 *      separator.  And non-existing folders are created as a psrt of the 
 *      process. 
 * 
 *  Arguments: 
 * 
 *      lpsmh               pointer to smh parent object 
 *      lpmdb               store in which the path is to exist 
 *      lpszPath            the root-based path to use 
 *      lppfldr     [OUT]   buffer to place target folder 
 *      lppvalEid   [OUT]   buffer for target entryid value struct pointer 
 * 
 *  Returns: 
 * 
 *      (HRESULT) 
 */ 
HRESULT 
HrFolderFromPath (LPSMH lpsmh, 
    LPMDB lpmdb, 
    LPTSTR lpszPath, 
    LPMAPIFOLDER FAR * lppfldr, 
    LPSPropValue FAR * lppvalEid) 
{ 
    HRESULT hr; 
    LPMAPIFOLDER lpfldr = NULL; 
    LPMAPIFOLDER lpfldrT = NULL; 
    LPSPropValue lpval = NULL; 
    LPTSTR lpch; 
    TCHAR rgch[MAX_PATH]; 
    ULONG ulType; 
 
    if (!LoadString (lpsmh->hinst, SMH_FolderComment, rgch, sizeof(rgch))) 
        rgch[0] = 0; 
 
    hr = HrGetOneProp ((LPMAPIPROP)lpmdb, PR_IPM_SUBTREE_ENTRYID, &lpval); 
    if (!HR_FAILED (hr)) 
    { 
        hr = lpmdb->lpVtbl->OpenEntry (lpmdb, 
                            lpval->Value.bin.cb, 
                            (LPENTRYID)lpval->Value.bin.lpb, 
                            NULL, 
                            MAPI_MODIFY, 
                            &ulType, 
                            (LPUNKNOWN FAR *)&lpfldr); 
 
        (*lpsmh->lpfnFree) (lpval); 
        lpval = NULL; 
 
        if (!HR_FAILED (hr)) 
        { 
            do 
            { 
                if (lpch = LpszFindChar (lpszPath, '\\')) 
                    *lpch = 0; 
 
                Assert (lstrlen (lpszPath)); 
                hr = lpfldr->lpVtbl->CreateFolder (lpfldr, 
                            FOLDER_GENERIC, 
                            lpszPath, 
                            rgch, 
                            NULL, 
                            MAPI_MODIFY | OPEN_IF_EXISTS, 
                            &lpfldrT); 
                if (HR_FAILED (hr)) 
                { 
#ifdef  DEBUG 
                    LPMAPIERROR lperr = NULL; 
                    lpfldr->lpVtbl->GetLastError (lpfldr, hr, 0, &lperr); 
                    DebugTrace ("SMH: WARNING: unable to open/create folder: '%s' in %s\n", 
                        lperr->lpszError, lperr->lpszComponent); 
                    (*lpsmh->lpfnFree) (lperr); 
#endif 
                    break; 
                } 
 
                UlRelease (lpfldr); 
                lpfldr = lpfldrT; 
                lpfldrT = NULL; 
 
                lpszPath = (lpch ? ++lpch : NULL); 
 
            } while (lpszPath); 
        } 
        if (!HR_FAILED (hr)) 
        { 
            hr = HrGetOneProp ((LPMAPIPROP)lpfldr, PR_ENTRYID, &lpval); 
            if (!HR_FAILED (hr)) 
            { 
                *lppfldr = lpfldr; 
                *lppvalEid = lpval; 
                lpfldr = NULL; 
                lpval = NULL; 
            } 
        } 
    } 
 
    (*lpsmh->lpfnFree) (lpval); 
    UlRelease (lpfldr); 
 
    DebugTraceResult (HrFolderFromPath(), hr); 
    return hr; 
} 
 
 
/* 
 *  HrBuildRule() 
 * 
 *  Purpose: 
 * 
 *      Takes a profile section and builds a rule structure that 
 *      corresponds to the properties in the profile section. 
 * 
 *  Arguments: 
 * 
 *      lpsmh           pointer to smh parent object 
 *      lpmuid          profile section UID 
 *      lpprl   [OUT]   buffer for the newly created rule pointer 
 * 
 *  Returns: 
 * 
 *      (HRESULT) 
 */ 
HRESULT 
HrBuildRule (LPSMH lpsmh, LPMAPIUID lpmuid, LPRULE FAR * lpprl) 
{ 
    SCODE sc; 
    HRESULT hr; 
    LPMAPISESSION lpsess = lpsmh->lpsess; 
    LPPROFSECT lpprof = NULL; 
    LPRULE lprl = NULL; 
    LPSPropValue lpval = NULL; 
    LPSPropValue lpvalEid = NULL; 
    LPSPropValue lpvalT; 
    LPSRestriction lpres = NULL; 
    ULONG cval; 
    ULONG ulType; 
    UINT cb; 
    UINT i; 
 
    sc = (*lpsmh->lpfnAlloc) (sizeof(RULE), &lprl); 
    if (FAILED (sc)) 
    { 
        hr = ResultFromScode (sc); 
        goto ret; 
    } 
    memset (lprl, 0, sizeof(RULE)); 
    memcpy (&lprl->muid, lpmuid, sizeof(MAPIUID)); 
    hr = lpsess->lpVtbl->OpenProfileSection (lpsess, 
                            lpmuid, 
                            NULL, 
                            MAPI_MODIFY, 
                            &lpprof); 
    if (HR_FAILED (hr)) 
        goto ret; 
 
    hr = lpprof->lpVtbl->GetProps (lpprof, 
                            (LPSPropTagArray)&sptRule, 
                            0, 
                            &cval, 
                            &lpval); 
    if (HR_FAILED (hr)) 
        goto ret; 
 
    if ((lpval[ipRLType].ulPropTag != PR_RULE_TYPE) || 
        (lpval[ipRLData].ulPropTag != PR_RULE_DATA) || 
        (lpval[ipRLFlags].ulPropTag != PR_RULE_FLAGS) || 
        (!(lpval[ipRLFlags].Value.l & (RULE_DELETE | RULE_NO_MOVE)) && 
          (lpval[ipRLType].ulPropTag != PR_RULE_TYPE) || 
          (lpval[ipRLPath].ulPropTag != PR_RULE_TARGET_PATH) || 
          (lpval[ipRLStore].ulPropTag != PR_RULE_STORE_DISPLAY_NAME))) 
    { 
        /*  Something very important is missing */ 
 
        hr = ResultFromScode (MAPI_E_UNCONFIGURED); 
        goto ret; 
    } 
    lprl->rlTyp = (UINT)lpval[ipRLType].Value.l; 
    lprl->ulFlags = lpval[ipRLFlags].Value.l; 
 
    /*  Get the filter value */ 
 
    sc = (*lpsmh->lpfnAllocMore) (lpval[ipRLData].Value.bin.cb, lprl, &lprl->lpszData); 
    if (FAILED (sc)) 
    { 
        hr = ResultFromScode (sc); 
        goto ret; 
    } 
    memcpy (lprl->lpszData, 
        lpval[ipRLData].Value.bin.lpb, 
        (UINT)lpval[ipRLData].Value.bin.cb); 
 
    /*  Get the sounds */ 
 
    if (lpval[ipRLFlags].Value.l & RULE_PLAY_SOUNDS) 
    { 
        for (i = 0; i < csndMax; i++) 
        { 
            if ((lpval[ipRLLoPri + i].ulPropTag == sptRule.aulPropTag[ipRLLoPri + i]) && 
                (cb = lstrlen (lpval[ipRLLoPri + i].Value.lpszA))) 
            { 
                cb += sizeof(TCHAR); 
                sc = (*lpsmh->lpfnAllocMore) (cb, lprl, (LPVOID FAR *)&lprl->rgszSnd[i]); 
                if (FAILED (sc)) 
                { 
                    hr = ResultFromScode (sc); 
                    goto ret; 
                } 
                lstrcpy (lprl->rgszSnd[i], lpval[ipRLLoPri + i].Value.lpszA); 
            } 
        } 
    } 
 
    /*  Fill in the auto response */ 
 
    if (lpval[ipRLFlags].Value.l & RULE_AUTO_RESPONSE) 
    { 
        if (lpval[ipRLRepFwdRTF].ulPropTag == PR_RULE_REP_FWD_RTF) 
        { 
            sc = (*lpsmh->lpfnAllocMore) (lpval[ipRLRepFwdRTF].Value.bin.cb, 
                                          lprl, 
                                          (LPVOID FAR *)&lprl->lpbRTF); 
            if (FAILED (sc)) 
            { 
                hr = ResultFromScode (sc); 
                goto ret; 
            } 
            lprl->cbRTF = lpval[ipRLRepFwdRTF].Value.bin.cb; 
            memcpy (lprl->lpbRTF, lpval[ipRLRepFwdRTF].Value.bin.lpb, (UINT)lprl->cbRTF); 
        } 
        else if (lpval[ipRLRepFwd].ulPropTag == PR_RULE_REP_FWD_TEXT) 
        { 
            cb = lstrlen (lpval[ipRLRepFwd].Value.lpszA) + sizeof(TCHAR); 
            sc = (*lpsmh->lpfnAllocMore) (cb, lprl, (LPVOID FAR *)&lprl->lpszAnno); 
            if (FAILED (sc)) 
            { 
                hr = ResultFromScode (sc); 
                goto ret; 
            } 
            lstrcpy (lprl->lpszAnno, lpval[ipRLRepFwd].Value.lpszA); 
        } 
 
        if (lpval[ipRLFlags].Value.l & RULE_AUTO_FORWARD) 
        { 
            if (lpval[ipRLFwdEid].ulPropTag != PR_RULE_FORWARD_RECIP_ENTRYID) 
            { 
                hr = ResultFromScode (MAPI_E_UNCONFIGURED); 
                goto ret; 
            } 
            else 
            { 
                sc = (*lpsmh->lpfnAlloc) (sizeof(SPropValue), &lprl->lpvalRecip); 
                if (!FAILED (sc)) 
                { 
                    sc = (*lpsmh->lpfnAllocMore) (lpval[ipRLFwdEid].Value.bin.cb, 
                        lprl->lpvalRecip, 
                        &lprl->lpvalRecip[0].Value.bin.lpb); 
                    if (!FAILED (sc)) 
                    { 
                        lprl->lpvalRecip[0].ulPropTag = PR_ENTRYID; 
                        lprl->lpvalRecip[0].Value.bin.cb = lpval[ipRLFwdEid].Value.bin.cb; 
                        memcpy (lprl->lpvalRecip[0].Value.bin.lpb, 
                                lpval[ipRLFwdEid].Value.bin.lpb, 
                                lpval[ipRLFwdEid].Value.bin.cb); 
                    } 
                } 
            } 
            if (FAILED (sc)) 
            { 
                hr = ResultFromScode (sc); 
                goto ret; 
            } 
        } 
    } 
 
    /*  See if we are not going to delete the message */ 
     
    if (!(lpval[ipRLFlags].Value.l & (RULE_DELETE | RULE_NO_MOVE))) 
    { 
        /*  Find the target folder */ 
 
        hr = HrOpenMdbFromName (lpsmh, lpval[ipRLStore].Value.LPSZ, &lprl->lpmdb); 
        if (HR_FAILED (hr)) 
            goto ret; 
 
        if ((lpval[ipRLEid].ulPropTag != PR_RULE_TARGET_ENTRYID) || 
            (HR_FAILED (lpsess->lpVtbl->OpenEntry (lpsess, 
                            lpval[ipRLEid].Value.bin.cb, 
                            (LPENTRYID)lpval[ipRLEid].Value.bin.lpb, 
                            NULL, 
                            MAPI_MODIFY, 
                            &ulType, 
                            (LPUNKNOWN FAR *)&lprl->lpfldr)))) 
        { 
            hr = HrFolderFromPath (lpsmh, 
                            lprl->lpmdb, 
                            lpval[ipRLPath].Value.LPSZ, 
                            &lprl->lpfldr, 
                            &lpvalEid); 
            if (HR_FAILED (hr)) 
                goto ret; 
 
            lpvalEid->ulPropTag = PR_RULE_TARGET_ENTRYID; 
            HrSetOneProp ((LPMAPIPROP)lpprof, lpvalEid); 
            lprl->lpvalEid = lpvalEid; 
        } 
        else 
        { 
            hr = HrGetOneProp ((LPMAPIPROP)lprl->lpfldr, PR_ENTRYID, &lprl->lpvalEid); 
            if (HR_FAILED (hr)) 
                goto ret; 
        } 
    } 
 
    if ((lpval[ipRLType].Value.l == RL_TO_RECIP) || 
        (lpval[ipRLType].Value.l == RL_CC_RECIP) || 
        (lpval[ipRLType].Value.l == RL_BCC_RECIP) || 
        (lpval[ipRLType].Value.l == RL_ANY_RECIP)) 
    { 
        cb = (sizeof(SRestriction) * cresMax) + (sizeof(SPropValue) * cvMax); 
        sc = (*lpsmh->lpfnAllocMore) (cb, lprl, &lpres); 
        if (FAILED (sc)) 
        { 
            hr = ResultFromScode (sc); 
            goto ret; 
        } 
        lpvalT = (LPSPropValue)&lpres[cresMax]; 
 
        lpres[iresAnd].rt = RES_AND; 
        lpres[iresAnd].res.resAnd.cRes = 2; 
        lpres[iresAnd].res.resAnd.lpRes = &lpres[iresRecip]; 
 
        lpvalT[ivRecip].ulPropTag = PR_RECIPIENT_TYPE; 
        lpvalT[ivRecip].Value.l = ((lpval[ipRLType].Value.l == RL_TO_RECIP) 
                                    ? MAPI_TO 
                                    : ((lpval[ipRLType].Value.l == RL_CC_RECIP) 
                                        ? MAPI_CC 
                                        : ((lpval[ipRLType].Value.l == RL_BCC_RECIP) 
                                            ? MAPI_BCC 
                                            : 0))); 
        lpres[iresRecip].rt = RES_PROPERTY; 
        lpres[iresRecip].res.resProperty.relop = RELOP_EQ; 
        lpres[iresRecip].res.resContent.ulPropTag = PR_RECIPIENT_TYPE; 
        lpres[iresRecip].res.resContent.lpProp = &lpvalT[ivRecip]; 
 
        lpres[iresOr].rt = RES_OR; 
        lpres[iresOr].res.resOr.cRes = 2; 
        lpres[iresOr].res.resOr.lpRes = &lpres[iresEmail]; 
 
        lpvalT[ivEmail].ulPropTag = PR_EMAIL_ADDRESS; 
        lpvalT[ivEmail].Value.LPSZ = (LPTSTR)lprl->lpszData; 
        lpres[iresEmail].rt = RES_CONTENT; 
        lpres[iresEmail].res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_IGNORECASE; 
        lpres[iresEmail].res.resContent.ulPropTag = PR_EMAIL_ADDRESS; 
        lpres[iresEmail].res.resContent.lpProp = &lpvalT[ivEmail]; 
 
        lpvalT[ivDispNm].ulPropTag = PR_DISPLAY_NAME; 
        lpvalT[ivDispNm].Value.LPSZ = (LPTSTR)lprl->lpszData; 
        lpres[iresDispNm].rt = RES_CONTENT; 
        lpres[iresDispNm].res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_IGNORECASE; 
        lpres[iresDispNm].res.resContent.ulPropTag = PR_DISPLAY_NAME; 
        lpres[iresDispNm].res.resContent.lpProp = &lpvalT[ivDispNm]; 
 
        if ((lpval[ipRLType].Value.l == RL_TO_RECIP) || 
            (lpval[ipRLType].Value.l == RL_CC_RECIP) || 
            (lpval[ipRLType].Value.l == RL_BCC_RECIP)) 
            lprl->lpres = &lpres[iresAnd]; 
        else 
            lprl->lpres = &lpres[iresOr]; 
    } 
 
ret: 
 
    (*lpsmh->lpfnFree) (lpval); 
 
    UlRelease (lpprof); 
    if (HR_FAILED (hr)) 
    { 
        (*lpsmh->lpfnFree) (lprl->lpvalRecip); 
        (*lpsmh->lpfnFree) (lprl->lpvalEid); 
        (*lpsmh->lpfnFree) (lprl); 
        lprl = NULL; 
    } 
    *lpprl = lprl; 
 
    DebugTraceResult (HrBuildRule(), hr); 
    return hr; 
} 
 
 
/* 
 *  ReleaseBkit() 
 * 
 *  Purpose: 
 * 
 *      Cleans up all resources held by a bucket structure and wipes the 
 *      structure clean. 
 * 
 *  Arguments: 
 * 
 *      lpsmh           pointer to the smh object (uses allocation fn's) 
 *      lpbkit          pointer to the bucket needing cleaning 
 * 
 */ 
VOID 
ReleaseBkit (LPSMH lpsmh, LPBKIT lpbkit) 
{ 
    UlRelease (lpbkit->lpfldr); 
    UlRelease (lpbkit->lpfldrYr); 
    UlRelease (lpbkit->lpfldrParent); 
    (*lpsmh->lpfnFree) (lpbkit->lpeid); 
    (*lpsmh->lpfnFree) (lpbkit->lpeidYr); 
    (*lpsmh->lpfnFree) (lpbkit->lpeidParent); 
    memset (lpbkit, 0, sizeof(BKIT)); 
    return; 
} 
 
 
HRESULT 
HrInitSMH (LPSMH lpsmh) 
{ 
    HRESULT hr; 
    LPPROFSECT lpprof = NULL; 
    LPRULE lprl; 
    LPSPropValue lpval = NULL; 
    LPSPropValue lpvalOld; 
    UINT crl; 
    ULONG cval; 
     
    /*  Get options from the profile */ 
 
    hr = lpsmh->lpsess->lpVtbl->OpenProfileSection (lpsmh->lpsess, 
                            &lpsmh->muid, 
                            NULL, 
                            MAPI_MODIFY, 
                            &lpprof); 
    if (HR_FAILED (hr)) 
        goto ret; 
 
    hr = lpprof->lpVtbl->GetProps (lpprof, 
                            (LPSPropTagArray)&sptConfigProps, 
                            0, 
                            &cval, 
                            &lpval); 
    if (HR_FAILED (hr)) 
        goto ret; 
 
    /*  Check to see if we are configured */ 
 
    if (lpval[ipFlags].ulPropTag != PR_SMH_FLAGS) 
    { 
        hr = ResultFromScode (MAPI_E_UNCONFIGURED); 
        goto ret; 
    } 
 
    /*  Check that the rules are stored in the correct format */ 
     
    if (lpval[ipRules].ulPropTag != PR_SMH_RULES) 
    { 
        hr = HrGetOneProp ((LPMAPIPROP)lpprof, 
                    CHANGE_PROP_TYPE(PR_SMH_RULES, PT_BINARY), 
                    &lpvalOld); 
        if (!HR_FAILED (hr)) 
        { 
            /*  The rules are stored in the wrong format */ 
 
            hr = HrUpdateProfileFormat (lpsmh->lpsess, 
                            lpsmh->lpsess->lpVtbl->OpenProfileSection, 
                            lpsmh->lpfnAllocMore, 
                            lpsmh->lpfnFree, 
                            lpval, 
                            lpvalOld); 
            (*lpsmh->lpfnFree) (lpvalOld); 
            if (HR_FAILED (hr)) 
                goto ret; 
            /*  Save out anything we got back */ 
 
            lpprof->lpVtbl->SetProps (lpprof, cpMax, lpval, NULL); 
        } 
    } 
    UlRelease (lpprof); 
    lpprof = NULL; 
 
    /*  Grab the exclusions list */ 
 
    hr = ResultFromScode (PropCopyMore (&lpsmh->valEx, 
                            &lpval[ipExc], 
                            lpsmh->lpfnAllocMore, 
                            lpsmh)); 
    if (HR_FAILED (hr)) 
        goto ret; 
 
    /*  Init the stores table */ 
 
    hr = HrInitStoresTable (lpsmh, lpsmh->lpsess); 
    if (HR_FAILED (hr)) 
        goto ret; 
 
    /*  Store the values */ 
 
    lpsmh->fAtp = !!(lpval[ipFlags].Value.l & SMH_ADD_TO_PAB); 
    lpsmh->fCatSm = !!(lpval[ipFlags].Value.l & SMH_FILTER_SENTMAIL); 
    lpsmh->fCatSmByYr = !!(lpval[ipFlags].Value.l & SMH_FILTER_SENTMAIL_YR); 
 
    /*  If we are archiving deleted mail, init the filters */ 
     
    if (lpval[ipFlags].Value.l & SMH_FILTER_DELETED) 
    { 
        HrInitDeletedMailFilter (lpsmh); 
        lpsmh->fCatWb = !!(lpval[ipFlags].Value.l & SMH_FILTER_DELETED_YR); 
    } 
     
    /*  If the unread folder is desired, make sure it is there */ 
     
    if (!!(lpval[ipFlags].Value.l & SMH_UNREAD_VIEWER)) 
        HrInitUnreadSearch (lpsmh); 
 
    /*  Setup the oof text */ 
     
    if ((lpval[ipOofEnabled].ulPropTag == PR_SMH_OOF_ENABLED) && 
        lpval[ipOofEnabled].Value.b) 
    { 
        lpsmh->fOof = !HR_FAILED (HrInitOof (lpsmh, 
                                        &lpval[ipOof], 
                                        &lpval[ipOofRtf])); 
    } 
 
    /*  Build the rules if need be */ 
     
    if ((lpval[ipFlags].Value.l & SMH_FILTER_INBOUND) && 
        (lpval[ipRules].ulPropTag == PR_SMH_RULES) && 
        (lpval[ipRules].Value.MVbin.cValues != 0)) 
    { 
        crl = (UINT)lpval[ipRules].Value.MVbin.cValues; 
        while (crl--) 
        { 
            hr = HrBuildRule (lpsmh, 
                    (LPMAPIUID)lpval[ipRules].Value.MVbin.lpbin[crl].lpb, 
                    &lprl); 
            if (!HR_FAILED (hr)) 
            { 
                lprl->rlNext = lpsmh->lstRl; 
                lpsmh->lstRl = lprl; 
            } 
        } 
        hr = hrSuccess; 
    } 
 
ret: 
 
    UlRelease (lpprof); 
    (*lpsmh->lpfnFree) (lpval); 
 
    DebugTraceResult (HrInitSMH(), hr); 
    return hr; 
} 
 
 
VOID 
DeinitSMH (LPSMH lpsmh) 
{ 
    LPRULE lprl; 
    LPRULE lprlT; 
    LPWB lpwb; 
    LPWB lpwbT; 
     
    ReleaseBkit (lpsmh, &lpsmh->bkitSm); 
    memset (&lpsmh->bkitSm, 0, sizeof(BKIT)); 
 
    for (lpwb = lpsmh->lstWb; lpwb; lpwb = lpwbT) 
    { 
        lpwbT = lpwb->wbNext; 
        lpwb->lptbl->lpVtbl->Unadvise (lpwb->lptbl, lpwb->ulAdvz); 
 
#ifdef  _WIN32 
        if (lpwb->ht) 
        { 
            lpwb->fBail = TRUE; 
            WaitForSingleObject (lpwb->ht, INFINITE); 
        } 
#endif 
 
        ReleaseBkit (lpsmh, &lpwb->bkit); 
        UlRelease (lpwb->lptbl); 
        UlRelease (lpwb->lpfldr); 
 
        (*lpsmh->lpfnFree) (lpwb->lpvalEid); 
        (*lpsmh->lpfnFree) (lpwb); 
 
    } 
    lpsmh->lstWb = NULL; 
    for (lprl = lpsmh->lstRl; lprl; lprl = lprlT) 
    { 
        lprlT = lprl->rlNext; 
        ReleaseBkit (lpsmh, &lprl->bkit); 
        UlRelease (lprl->lpfldr); 
        (*lpsmh->lpfnFree) (lprl->lpvalEid); 
        (*lpsmh->lpfnFree) (lprl->lpvalRecip); 
        (*lpsmh->lpfnFree) (lprl); 
    } 
    lpsmh->lstRl = NULL; 
 
    UlRelease (lpsmh->oof.lptad); 
    UlRelease (lpsmh->oof.lptbl); 
    (*lpsmh->lpfnFree) (lpsmh->oof.lpbRTF); 
    (*lpsmh->lpfnFree) (lpsmh->oof.lpszBody); 
    memset (&lpsmh->oof, 0, sizeof(OOF)); 
 
    ReleaseStoresTable (lpsmh); 
    return; 
} 
 
 
/* 
 *  SMH Object Methods 
 * 
 *  SMH_QueryInterface      (See OLE IUnknown object methods) 
 *  SMH_AddRef              (See OLE IUnknown object methods) 
 *  SMH_Release             (See OLE IUnknown object methods) 
 *  SMH_InboundMsgHook      Filters inbound messages 
 *  SMH_OutboundMsgHook     Filters sent mail messages 
 * 
 */ 
STDMETHODIMP 
SMH_QueryInterface (LPSMH lpsmh, REFIID lpiid, LPVOID FAR * lppv) 
{ 
    if (IsBadWritePtr (lpsmh, sizeof(SMH)) || 
        IsBadReadPtr (lpiid, sizeof(IID)) || 
        IsBadWritePtr (lppv, sizeof(LPVOID))) 
        return ResultFromScode (MAPI_E_INVALID_PARAMETER); 
 
    if (!memcmp (lpiid, &IID_ISpoolerHook, sizeof(IID)) || 
        !memcmp (lpiid, &IID_IUnknown, sizeof(IID))) 
    { 
        *lppv = (LPVOID)lpsmh; 
        lpsmh->lcInit++; 
        return hrSuccess; 
    } 
    DebugTraceSc (SMH_QueryInterface(), MAPI_E_INTERFACE_NOT_SUPPORTED); 
    return ResultFromScode (MAPI_E_INTERFACE_NOT_SUPPORTED); 
} 
 
STDMETHODIMP_ (ULONG) 
SMH_AddRef (LPSMH lpsmh) 
{ 
    if (IsBadWritePtr (lpsmh, sizeof(SMH))) 
        return 0; 
 
    return ++lpsmh->lcInit; 
} 
 
STDMETHODIMP_ (ULONG) 
SMH_Release (LPSMH lpsmh) 
{ 
    if (IsBadWritePtr (lpsmh, sizeof(SMH))) 
        return 0; 
 
    if (--lpsmh->lcInit) 
        return lpsmh->lcInit; 
 
    DeinitSMH (lpsmh); 
     
    CloseHandle (lpsmh->hevtCfg); 
    UlRelease (lpsmh->lpsess); 
    (*lpsmh->lpfnFree) (lpsmh); 
    if (lpCtl3D) 
    { 
        CTL3D_Uninitialize (lpCtl3D); 
        lpCtl3D = NULL; 
    } 
    return 0; 
} 
 
/* 
 *  SMH_InboundMsgHook() 
 * 
 *  Purpose: 
 * 
 *      The purpose of this filter is to match inbound messages to 
 *      individual rules from the profile and re-route the messages based 
 *      on the results of the comparisons. 
 * 
 *  Arguments: 
 * 
 *      lpsmh           this filter hook obj 
 *      lpmsg           the message to be filtered 
 *      lpfldrDef       owning folder of message 
 *      lpmdbDef        owning store of message 
 *      lpulFlags       flags returned by filter 
 *      lpcbeid         cb for entryid of default target for message 
 *      lppbeid         pb for entryid of default target for message 
 * 
 *  Operation: 
 * 
 *      Opens the suggested folder (if needed) and checks for the 
 *      existence of the appropriate "bucket" folder.  If it does exist, 
 *      then the  folder is created and cached.  The entryid is grabbed 
 *      and passed back in to the spooler. 
 * 
 *  Returns: 
 * 
 *      (HRESULT) 
 *      lpulFlags   [out]   set HOOK_CANCEL if this is the last hook 
 *      lpcbeid     [out]   the size of the returned EntryID 
 *      lppbeid     [out]   the data of the returned EntryID 
 * 
 */ 
STDMETHODIMP 
SMH_InboundMsgHook (LPSMH lpsmh, 
    LPMESSAGE lpmsg, 
    LPMAPIFOLDER lpfldrDef, 
    LPMDB lpmdbDef, 
    ULONG FAR * lpulFlags, 
    ULONG FAR * lpcbeid, 
    LPBYTE FAR * lppeid) 
{ 
    HRESULT hr = hrSuccess; 
    LPRULE lprl; 
    LPBYTE lpeid; 
    LPSPropValue lpval = NULL; 
 
    /*  Quick and dirty parameter check */ 
 
    if (IsBadWritePtr (lpsmh, sizeof(SMH)) || 
        IsBadWritePtr (lpcbeid, sizeof(ULONG)) || 
        IsBadWritePtr (lppeid, sizeof(LPBYTE)) || 
        IsBadWritePtr (*lppeid, (UINT)(*lpcbeid))) 
        return ResultFromScode (MAPI_E_INVALID_PARAMETER); 
 
#ifdef  _WIN32 
    if (FConfigChanged (lpsmh->hevtCfg)) 
    { 
        /*  reconfiguration required */ 
         
        DeinitSMH (lpsmh); 
        hr = HrInitSMH (lpsmh); 
        if (HR_FAILED (hr)) 
            return hr; 
         
        ResetEvent (lpsmh->hevtCfg); 
    } 
#endif  // _WIN32 
     
    if (lprl = lpsmh->lstRl)    /*  Yup '=' */ 
    { 
        hr = HrCheckExclusions (lpsmh, lpmsg); 
        if (!HR_FAILED (hr)) 
        { 
            /*  We have not been excluded */ 
 
            for (; lprl; lprl = lprl->rlNext) 
            { 
                hr = HrCheckRule (lpsmh, lprl, lpmsg, &lpval); 
                if (!HR_FAILED (hr)) 
                { 
                    /*  We have a match. What do we do, filter or delete? */ 
                     
                    if (!(lprl->ulFlags & (RULE_DELETE | RULE_NO_MOVE))) 
                    { 
                        /*  Filter the critter */ 
 
                        hr = ResultFromScode ((*lpsmh->lpfnAlloc) (lprl->lpvalEid->Value.bin.cb, &lpeid)); 
                        if (!HR_FAILED (hr)) 
                        { 
                            memcpy (lpeid, lprl->lpvalEid->Value.bin.lpb, 
                                (UINT)lprl->lpvalEid->Value.bin.cb); 
 
                            (*lpsmh->lpfnFree) (*lppeid); 
                            *lpcbeid = lprl->lpvalEid->Value.bin.cb; 
                            *lppeid = lpeid; 
 
                            if (lprl->ulFlags & RULE_ARCHIVED) 
                            { 
                                hr = HrArchiveMessage (lpsmh, 
                                            lpmsg, 
                                            lpfldrDef, 
                                            lpmdbDef, 
                                            &lprl->bkit, 
                                            !!(lprl->ulFlags & RULE_ARCHIVED_BY_YEAR), 
                                            lpcbeid, 
                                            lppeid); 
                                 
                                if (lprl->ulFlags & RULE_TERMINAL) 
                                    *lpulFlags = HOOK_CANCEL; 
                            } 
                        } 
 
                        /*  Play the sound */ 
 
                        if ((lpval[ipPriority].Value.l < 3) && 
                            lprl->rgszSnd[lpval[ipPriority].Value.l]) 
                            sndPlaySound (lprl->rgszSnd[lpval[ipPriority].Value.l], SND_ASYNC); 
                    } 
                    else 
                    { 
                        if (lprl->ulFlags & RULE_DELETE) 
                            *lpulFlags |= HOOK_DELETE; 
                                           
                        *lpulFlags |= HOOK_CANCEL; 
                    } 
 
                    /*  Check for an auto-forward\reply */ 
 
                    if (lprl->ulFlags & RULE_AUTO_RESPONSE) 
                    { 
                        /*  We have a response that needs to 
                         *  be created, and sent on. 
                         */ 
                        hr = HrGenerateResponse (lpsmh, lprl, lpfldrDef, lpmsg); 
                    } 
                    break; 
                } 
                else if (GetScode (hr) != MAPI_E_NOT_ME) 
                { 
                    /*  We have a failure that is not really 
                     *  expected, we need to bail.  Also, this 
                     *  should cancel any further hooking 
                     */ 
                    *lpulFlags = HOOK_CANCEL; 
                    break; 
                } 
                else 
                    hr = hrSuccess; 
            } 
        } 
    } 
     
    if (lpsmh->fOof) 
    { 
        /*  OOF'ing is done by calling HrGenerateResponse() 
         *  and passing in a NULL for the rule. 
         */ 
        hr = HrGenerateResponse (lpsmh, NULL, lpfldrDef, lpmsg); 
    } 
 
    (*lpsmh->lpfnFree) (lpval); 
    DebugTraceResult (SMH_InboundMsgHook(), hr); 
    return hrSuccess; 
} 
 
 
/* 
 *  SMH_OutboundMsgHook() 
 * 
 *  Purpose: 
 * 
 *      The purpose of this filter is to "hash" a users sent mail 
 *      processing based on date.  The most obvious bucket size is 
 *      monthly but there is no reason not to make this an option the 
 *      user could confiigure. 
 * 
 *  Arguments: 
 * 
 *      lpsmh           this filter hook obj 
 *      lpmsg           the message to be filtered 
 *      lpfldrDef       owning folder of message 
 *      lpmdbDef        owning store of message 
 *      lpulFlags       flags returned by filter 
 *      lpcbeid         cb for entryid of default target for message 
 *      lppbeid         pb for entryid of default target for message 
 * 
 *  Operation: 
 * 
 *      Opens the suggested folder (if needed) and checks for the 
 *      existence of the appropriate "bucket" folder.  If it does exist, 
 *      then the  folder is created and cached.  The entryid is grabbed 
 *      and passed back in to the spooler. 
 * 
 *  Returns: 
 * 
 *      (HRESULT) 
 *      lpulFlags   [out]   set HOOK_CANCEL if this is the last hook 
 *      lpcbeid     [out]   the size of the returned EntryID 
 *      lppbeid     [out]   the data of the returned EntryID 
 * 
 */ 
STDMETHODIMP 
SMH_OutboundMsgHook (LPSMH lpsmh, 
    LPMESSAGE lpmsg, 
    LPMAPIFOLDER lpfldrDef, 
    LPMDB lpmdbDef, 
    ULONG FAR * lpulFlags, 
    ULONG FAR * lpcbeid, 
    LPBYTE FAR * lppeid) 
{ 
    HRESULT hr = hrSuccess; 
 
    /*  Quick and dirty parameter check */ 
 
    if (IsBadWritePtr (lpsmh, sizeof(SMH)) || 
        IsBadWritePtr (lpcbeid, sizeof(ULONG)) || 
        IsBadWritePtr (lppeid, sizeof(LPBYTE)) || 
        IsBadWritePtr (*lppeid, (UINT)(*lpcbeid))) 
        return ResultFromScode (MAPI_E_INVALID_PARAMETER); 
 
#ifdef  _WIN32 
    if (FConfigChanged (lpsmh->hevtCfg)) 
    { 
        /*  reconfiguration required */ 
         
        DeinitSMH (lpsmh); 
        hr = HrInitSMH (lpsmh); 
        if (HR_FAILED (hr)) 
            return hr; 
         
        ResetEvent (lpsmh->hevtCfg); 
    } 
#endif  // _WIN32 
 
    if (lpsmh->fAtp) 
        (void) HrAddEntriesToPab (lpsmh, lpmsg); 
 
    if (lpsmh->fCatSm) 
        hr = HrArchiveMessage (lpsmh, 
                lpmsg, 
                lpfldrDef, 
                lpmdbDef, 
                &lpsmh->bkitSm, 
                lpsmh->fCatSmByYr, 
                lpcbeid, 
                lppeid); 
 
    DebugTraceResult (SMH_OutboundMsgHook(), hr); 
    return hrSuccess; 
} 
 
 
/* 
 *  SMH_Init() 
 * 
 *  Purpose: 
 * 
 *      Spooler's entry into the sample mail handler.  This function is 
 *      equivilent to a provider logon in that it returns an object to 
 *      the spooler that will be used to make any additional calls into 
 *      the handler. 
 * 
 *  Arguments: 
 * 
 *      lpsess          the session this handler relates to 
 *      hinst           hinst of the SMH dll 
 *      lpfnAlloc       pointer to MAPIAllocateBuffer() 
 *      lpfnAllocMore   pointer to MAPIAllocateMore() 
 *      lpfnFree        pointer to MAPIFreeBuffer() 
 *      lpmuid          pointer to profile section muid 
 *      ulFlags         flags 
 *      lppHook         buffer to hold handler object 
 * 
 *  Returns: 
 * 
 *      (HRESULT) 
 *      lpphook     [OUT]   holds the returned handler object iff successful 
 */ 
STDINITMETHODIMP 
SMH_Init (LPMAPISESSION lpsess, 
    HINSTANCE hinst, 
    LPALLOCATEBUFFER lpfnAlloc, 
    LPALLOCATEMORE lpfnAllocMore, 
    LPFREEBUFFER lpfnFree, 
    LPMAPIUID lpmuid, 
    ULONG ulFlags, 
    LPSPOOLERHOOK FAR * lppHook) 
{ 
    SCODE sc; 
    LPSMH lpsmh = NULL; 
    HRESULT hr; 
 
    sc = (*lpfnAlloc) (sizeof(SMH), &lpsmh); 
    if (FAILED (sc)) 
        return ResultFromScode (sc); 
    memset (lpsmh, 0, sizeof(SMH)); 
 
    hr = lpsess->lpVtbl->QueryInterface (lpsess, 
                            &IID_IMAPISession, 
                            &lpsmh->lpsess); 
    if (!HR_FAILED (hr)) 
    { 
        /*  Fill in all fields of the object */ 
 
        lpsmh->lpVtbl = (SMH_Vtbl FAR *)&vtblSMH; 
        lpsmh->lcInit = 1; 
        lpsmh->hinst = hinst; 
        lpsmh->lpsess = lpsess; 
        lpsmh->lpfnAlloc = lpfnAlloc; 
        lpsmh->lpfnAllocMore = lpfnAllocMore; 
        lpsmh->lpfnFree = lpfnFree; 
        memcpy (&lpsmh->muid, lpmuid, sizeof(MAPIUID)); 
 
#ifdef  _WIN32 
        /*  Setup listening for configuration changes */ 
 
        (void)HrGetConfigEvent (&lpsmh->hevtCfg); 
#endif 
 
        /*  Fill out the rest of the structure */ 
         
        hr = HrInitSMH (lpsmh); 
    } 
 
    if (HR_FAILED (hr)) 
    { 
        UlRelease (lpsmh); 
        lpsmh = NULL; 
    } 
    *lppHook = (LPSPOOLERHOOK)lpsmh; 
    DebugTraceResult (SMH_Init(), hr); 
    return hr; 
}