/*
 *  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.
 *
 *    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.
 *
 *  Copyright 1992-95 Microsoft Corporation.  All Rights Reserved.
 */

#include "_pch.h"

/*
 *  FIsLeapYear()
 *
 *  Used to calculate leap years when determining month ranges for
 *  archive folders.
 */
#define FIsLeapYear(_yr) ((!((_yr) % 400) || ((_yr) % 100) && !((_yr) % 4)) ? TRUE : FALSE)

/*
 *  sptMsgDates
 *
 *  The list of properties that are used for archiving.  If
 *  PR_MESSAGE_DELIVERY_TIME is not available, then SMH will use
 *  PR_CLIENT_SUBMIT_TIME.
 */
const static SizedSPropTagArray(2, sptMsgDates) =
{
    2,
    {
        PR_MESSAGE_DELIVERY_TIME,
        PR_CLIENT_SUBMIT_TIME
    }
};

/*
 *  sptFldrDates
 *
 *  Theses properties describe the range of dates that an archive folder
 *  with the returned EntryID will support.  This is used in the call to
 *  find a supporting sub-folder.
 */
const static SizedSPropTagArray (3, sptFldrDates) =
{
    3,
    {
        PR_START_DATE,
        PR_END_DATE,
        PR_ENTRYID
    }
};

/*
 *  rgtstrMonth
 *  rgtstrMonthFull
 *  rgwDaysPerMonth
 *
 *  These arrays are used in the calculation and creation of supporting
 *  archive folders.
 */
const static TCHAR * rgtstrMonth[] =
{
    TEXT ("Jan"),
    TEXT ("Feb"),
    TEXT ("Mar"),
    TEXT ("Apr"),
    TEXT ("May"),
    TEXT ("Jun"),
    TEXT ("Jul"),
    TEXT ("Aug"),
    TEXT ("Sep"),
    TEXT ("Oct"),
    TEXT ("Nov"),
    TEXT ("Dec")
};
const static TCHAR * rgtstrMonthFull[] =
{
    TEXT ("January"),
    TEXT ("February"),
    TEXT ("March"),
    TEXT ("April"),
    TEXT ("May"),
    TEXT ("June"),
    TEXT ("July"),
    TEXT ("August"),
    TEXT ("September"),
    TEXT ("October"),
    TEXT ("November"),
    TEXT ("December")
};
const static WORD rgwDaysPerMonth[] =
{
    31, //  JAN
    28, //  FEB
    31, //  MAR
    30, //  APR
    31, //  MAY
    30, //  JUN
    31, //  JUL
    31, //  AUG
    30, //  SEP
    31, //  OCT
    30, //  NOV
    31  //  DEC
};

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

extern LPTSTR lpszConfigEvt;
extern SPropTagArray sptRule;
extern SPropTagArray sptConfigProps;

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


/*
 *  HrCacheFolder()
 *
 *  Purpose:
 *
 *      Caches the passed in entryid along with a matching folder
 *
 *  Arguments:
 *
 *      lpsmh       pointer to the sentmail handler
 *      cbeid       count of bytes for the entryid to check
 *      lpeid       data for the entryid to check
 *      lpcbeid     points to the cached entryid size
 *      lppeid      points to the cached entryid data
 *      lppfldr     points to the cached mapi folder object
 *      lpfUpdated  points to cache update flag
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpcbeid     [OUT] size of newly cached entryid
 *      lppeid      [OUT] data of newly cached entryid
 *      lppfldr     [OUT] folder corresponding to cached entryid
 *      lpfUpdated  [OUT] TRUE iff the out folder is not the
 *                          previously cached folder
 */
HRESULT
HrCacheFolder (LPSMH lpsmh,
    ULONG cbeid,
    LPENTRYID lpeid,
    ULONG FAR * lpcbeid,
    LPENTRYID FAR * lppeid,
    LPMAPIFOLDER FAR * lppfldr,
    BOOL far * lpfUpdated)
{
    HRESULT hr;
    ULONG ulType;
    ULONG ulMatch;
    LPMAPIPROP lpmp = NULL;

    /* Init the update flag */

    *lpfUpdated = FALSE;

    /*  Is the topmost sent mail folder the same folder
     *  as the last filtered message?
     */
    hr = lpsmh->lpsess->lpVtbl->CompareEntryIDs (lpsmh->lpsess,
                                        cbeid,
                                        lpeid,
                                        *lpcbeid,
                                        *lppeid,
                                        0,
                                        &ulMatch);
    if (HR_FAILED (hr) || !ulMatch)
    {
        /* Different folder, guess we better toss the cached one */

        (*lpsmh->lpfnFree) (*lppeid);
        *lppeid = NULL;
        *lpcbeid = 0;

        /* Cache the SentMail */

        hr = ResultFromScode ((*lpsmh->lpfnAlloc) (cbeid, lppeid));
        if (HR_FAILED (hr))
            goto ret;
        memcpy (*lppeid, lpeid, (UINT)cbeid);
        *lpcbeid = cbeid;
        *lpfUpdated = TRUE;
    }
    else if (*lppfldr)
        return hrSuccess;

    /* Open the new folder */

    hr = lpsmh->lpsess->lpVtbl->OpenEntry (lpsmh->lpsess,
                                        cbeid,
                                        lpeid,
                                        NULL,
                                        MAPI_BEST_ACCESS,
                                        &ulType,
                                        (LPUNKNOWN FAR *)&lpmp);
    if (HR_FAILED (hr))
        goto ret;

    if (ulType != MAPI_FOLDER)
    {
        hr = ResultFromScode (MAPI_E_UNEXPECTED_TYPE);
        goto ret;
    }

ret:

    if (HR_FAILED (hr))
    {
        UlRelease (lpmp);
        (*lpsmh->lpfnFree) (*lppeid);
        *lppeid = NULL;
        *lpcbeid = 0;
        lpmp = NULL;
    }

    UlRelease (*lppfldr);
    *lppfldr = (LPMAPIFOLDER)lpmp;

    DebugTraceResult (HrCacheFolder(), hr);
    return hr;
}


/*
 *  HrCreateHashedFolder()
 *
 *  Purpose:
 *
 *      Create/Caches the a folder that satisfies the hash value
 *
 *  Arguments:
 *
 *      lpsmh       pointer to the sentmail handler
 *      lpdft       pointer the the hash interval
 *      lpfldrPar   parnet folder
 *      lpszName    name for folder
 *      lpszComment comment for folder
 *      lpcbeid     points to the cached entryid size
 *      lppeid      points to the cached entryid data
 *      lppfldr     points to the cached mapi folder object
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpcbeid     [OUT] size of newly cached entryid
 *      lppeid      [OUT] data of newly cached entryid
 *      lppfldr     [OUT] folder corresponding to cached entryid
 */
HRESULT
HrCreateHashedFolder (LPSMH lpsmh,
    LPDFT lpdft,
    LPMAPIFOLDER lpfldrPar,
    LPTSTR lpszName,
    LPTSTR lpszComment,
    ULONG FAR * lpcbeid,
    LPENTRYID FAR * lppeid,
    LPMAPIFOLDER FAR * lppfldr)
{
    HRESULT hr;
    LPMAPIFOLDER lpfldr = NULL;
    LPSPropValue lpval = NULL;
    SPropValue rgval[2] = {0};
    ULONG cval;

    /* Toss the current cache info */

    (*lpsmh->lpfnFree) (*lppeid);
    *lppeid = NULL;
    *lpcbeid = 0;

    /* Create the new folder */

    hr = lpfldrPar->lpVtbl->CreateFolder (lpfldrPar,
                            FOLDER_GENERIC,
                            lpszName,
                            lpszComment,
                            NULL,
                            OPEN_IF_EXISTS,
                            &lpfldr);
    if (HR_FAILED (hr))
        goto ret;

    /* Set the hashing interval properties */

    rgval[0].ulPropTag = PR_START_DATE;
    rgval[0].Value.ft = lpdft->ftStart;
    rgval[1].ulPropTag = PR_END_DATE;
    rgval[1].Value.ft = lpdft->ftEnd;
    hr = lpfldr->lpVtbl->SetProps (lpfldr, 2, rgval, NULL);
    if (HR_FAILED (hr))
        goto ret;

    /* Cache the folder info */

    hr = lpfldr->lpVtbl->GetProps (lpfldr,
                            (LPSPropTagArray)&sptFldrDates,
                            FALSE,
                            &cval,
                            &lpval);
    if (HR_FAILED (hr))
        goto ret;

    /* Make sure we have all the info we need */

    if ((lpval[0].ulPropTag != PR_START_DATE) ||
        (lpval[1].ulPropTag != PR_END_DATE) ||
        (lpval[2].ulPropTag != PR_ENTRYID))
    {
        hr = ResultFromScode (MAPI_E_BAD_VALUE);
        goto ret;
    }

    /* Cache the entryid */

    hr = ResultFromScode ((*lpsmh->lpfnAlloc) (lpval[2].Value.bin.cb, lppeid));
    if (HR_FAILED (hr))
        goto ret;
    memcpy (*lppeid, lpval[2].Value.bin.lpb, (UINT)lpval[2].Value.bin.cb);
    *lpcbeid = lpval[2].Value.bin.cb;

ret:

    (*lpsmh->lpfnFree) (lpval);
    if (HR_FAILED (hr))
    {
        UlRelease (lpfldr);
        lpfldr = NULL;
    }
    UlRelease (*lppfldr);
    *lppfldr = lpfldr;

    DebugTraceResult (HrCreateHashedFolder(), hr);
    return hr;
}


/*
 *  HrCacheHashedFolder()
 *
 *  Purpose:
 *
 *      Caches the folder that matches the hash value (file time)
 *
 *  Arguments:
 *
 *      lpsmh       pointer to the sentmail handler
 *      ft          hash filetime
 *      lpdft       pointer the the hash interval
 *      lpcbeid     points to the cached entryid size
 *      lppeid      points to the cached entryid data
 *      lppfldr     points to the cached mapi folder object
 *      lpfUpdated  points to cache update flag
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpcbeid     [OUT] size of newly cached entryid
 *      lppeid      [OUT] data of newly cached entryid
 *      lppfldr     [OUT] folder corresponding to cached entryid
 *      lpfUpdated  [OUT] TRUE iff the out folder is not the
 *                          previously cached folder
 */
HRESULT
HrCacheHashedFolder (LPSMH lpsmh,
    FILETIME ft,
    LPDFT lpdft,
    LPMAPIFOLDER lpfldr,
    ULONG FAR * lpcbeid,
    LPENTRYID FAR * lppeid,
    LPMAPIFOLDER FAR * lppfldr,
    BOOL far * lpfUpdated)
{
    HRESULT hr;
    LPMAPIPROP lpmp = NULL;
    LPMAPITABLE lptbl = NULL;
    LPSRow lprw = NULL;
    LPSRowSet lprws = NULL;
    ULONG ulType;
    UINT i;

    /*  Check to see if the new hash fits the
     *  the current hashed folder, if the hash
     *  value does not work, find one that does
     */
    if (!*lpfUpdated &&
        (CompareFileTime (&lpdft->ftStart, &ft) != 1) &&
        (CompareFileTime (&lpdft->ftEnd, &ft) != -1))
    {
        /* The hash works, but do we have a folder? */

        if (*lppfldr)
            return hrSuccess;

        hr = lpsmh->lpsess->lpVtbl->OpenEntry (lpsmh->lpsess,
                                        *lpcbeid,
                                        *lppeid,
                                        NULL,
                                        MAPI_BEST_ACCESS,
                                        &ulType,
                                        (LPUNKNOWN FAR *)&lpmp);
        if (!HR_FAILED (hr) && (ulType != MAPI_FOLDER))
        {
            hr = ResultFromScode (MAPI_E_UNEXPECTED_TYPE);
            UlRelease (lpmp);
            lpmp = NULL;
        }
        goto ret;
    }

    /* Toss the cached info */

    (*lpsmh->lpfnFree) (*lppeid);
    *lppeid = NULL;
    *lpcbeid = 0;

    /* Get the hierachy and set it up to find the target folder */

    hr = lpfldr->lpVtbl->GetHierarchyTable (lpfldr, 0, &lptbl);
    if (HR_FAILED (hr))
        goto ret;

    hr = lptbl->lpVtbl->SetColumns (lptbl, (LPSPropTagArray)&sptFldrDates, 0);
    if (HR_FAILED (hr))
        goto ret;

    hr = lptbl->lpVtbl->QueryRows (lptbl, 12, 0L, &lprws);
    if (HR_FAILED (hr))
        goto ret;

    while (lprws->cRows)
    {
        for (i = 0; i < lprws->cRows; i++)
        {
            lprw = &lprws->aRow[i];

            if (!lpmp &&
                (lprw->lpProps[0].ulPropTag == PR_START_DATE) &&
                (lprw->lpProps[1].ulPropTag == PR_END_DATE) &&
                (CompareFileTime (&lprw->lpProps[0].Value.ft, &ft) != 1) &&
                (CompareFileTime (&lprw->lpProps[1].Value.ft, &ft) != -1))
            {
                /* Hey, this looks like the folder we want */

                hr = HrCacheFolder (lpsmh,
                        lprw->lpProps[2].Value.bin.cb,
                        (LPENTRYID)lprw->lpProps[2].Value.bin.lpb,
                        lpcbeid,
                        lppeid,
                        (LPMAPIFOLDER FAR *)&lpmp,
                        lpfUpdated);
                if (!HR_FAILED (hr))
                {
                    lpdft->ftStart = lprw->lpProps[0].Value.ft;
                    lpdft->ftEnd = lprw->lpProps[1].Value.ft;
                }
            }
        }

        /* Clean up the row properies */

        for (i = 0; i < lprws->cRows; i++)
            (*lpsmh->lpfnFree) (lprws->aRow[i].lpProps);

        /* We either found the folder or we had an error */

        if (lpmp || HR_FAILED (hr))
            break;

        /* Clean up the row set */

        (*lpsmh->lpfnFree) (lprws);
        hr = lptbl->lpVtbl->QueryRows (lptbl, 12, 0L, &lprws);
        if (HR_FAILED (hr))
            goto ret;
    }

    /* Clean up the final row set */

    (*lpsmh->lpfnFree) (lprws);

ret:

    UlRelease (lptbl);
    UlRelease (*lppfldr);
    *lppfldr = (LPMAPIFOLDER)lpmp;

    DebugTraceResult (HrCacheHashedFolder(), hr);
    return hr ? hr : (lpmp ? hrSuccess : ResultFromScode (MAPI_E_NOT_FOUND));
}


/*
 *  HrArchiveMessage()
 *
 *  Purpose:
 *
 *      The purpose of this function is to "hash" a single message by
 *      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       the owning folder of the message
 *      lpmdbDef        the owning store of the message
 *      lpbkit          the cached bucket structure
 *      fCatByYear      uses yearly subfolders iff TRUE
 *      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.
 *
 *      IMPORTANT: the entryid passed in will be swapped out by this
 *      call.  Therefore the *lppeid buffer must be allocated with the
 *      MAPIAllocateBuffer provider to the filter.
 *
 *  Returns:
 *
 *      (HRESULT)
 *      lpcbeid [out]   the size of the returned EntryID
 *      lppbeid [out]   the data of the returned EntryID
 *
 */
HRESULT
HrArchiveMessage (LPSMH lpsmh,
    LPMESSAGE lpmsg,
    LPMAPIFOLDER lpfldrDef,
    LPMDB lpmdbDef,
    LPBKIT lpbkit,
    BOOL fCatByYear,
    ULONG FAR * lpcbeid,
    LPBYTE FAR * lppeid)
{
    HRESULT hr = hrSuccess;
    BOOL fUpdated = FALSE;
    FILETIME ft;
    LPMAPIFOLDER lpfldr;
    LPSPropValue lpval = NULL;
    SYSTEMTIME st;
    TCHAR rgchName[64] = {0};
    ULONG cval;

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

    /* Get the date used by the hash */

    hr = lpmsg->lpVtbl->GetProps (lpmsg,
                            (LPSPropTagArray)&sptMsgDates,
                            FALSE,
                            &cval,
                            &lpval);
    if (HR_FAILED (hr))
        goto ret;

    /* Make sure what we end up with is usable */

    if (lpval[0].ulPropTag == PR_MESSAGE_DELIVERY_TIME)
    {
        DebugTrace ("SMH: filtering on PR_MESSAGE_DELIVERY_TIME\n");
        ft = lpval[0].Value.ft;
    }
    else if (lpval[1].ulPropTag == PR_CLIENT_SUBMIT_TIME)
    {
        DebugTrace ("SMH: filtering on PR_CLIENT_SUBMIT_TIME\n");
        ft = lpval[0].Value.ft;
    }
    else
    {
        DebugTrace ("SMH: cannot filter on provided time props\n");
        hr = ResultFromScode (MAPI_E_BAD_VALUE);
        goto ret;
    }
    if (!FileTimeToSystemTime (&ft, &st))
    {
        hr = ResultFromScode (MAPI_E_BAD_VALUE);
        goto ret;
    }

    /* Cache the parent folder */

    hr = HrCacheFolder (lpsmh,
                    *lpcbeid,
                    (LPENTRYID)*lppeid,
                    &lpbkit->cbeidParent,
                    &lpbkit->lpeidParent,
                    &lpbkit->lpfldrParent,
                    &fUpdated);
    if (HR_FAILED (hr))
        goto ret;

    if (fCatByYear)
    {
        /* Cache the year folder */

        hr = HrCacheHashedFolder (lpsmh,
                    ft,
                    &lpbkit->dftYr,
                    lpbkit->lpfldrParent,
                    &lpbkit->cbeidYr,
                    &lpbkit->lpeidYr,
                    &lpbkit->lpfldrYr,
                    &fUpdated);
        if (HR_FAILED (hr) && (GetScode (hr) == MAPI_E_NOT_FOUND))
        {
            wsprintf (rgchName, "%04hu", st.wYear);
            st.wMonth = 1;
            st.wDayOfWeek = 0;
            st.wDay = 1;
            st.wHour = 0;
            st.wMinute = 0;
            st.wSecond = 0;
            st.wMilliseconds = 0;
            if (!SystemTimeToFileTime (&st, &lpbkit->dftYr.ftStart))
            {
                hr = ResultFromScode (MAPI_E_BAD_VALUE);
                goto ret;
            }
            st.wDay = rgwDaysPerMonth[st.wMonth - 1];
            st.wMonth = 12;
            st.wDayOfWeek = 0;
            st.wDay = 31;
            st.wHour = 23;
            st.wMinute = 59;
            st.wSecond = 59;
            st.wMilliseconds = 999;
            if (!SystemTimeToFileTime (&st, &lpbkit->dftYr.ftEnd))
            {
                hr = ResultFromScode (MAPI_E_BAD_VALUE);
                goto ret;
            }
            hr = HrCreateHashedFolder (lpsmh,
                            &lpbkit->dftYr,
                            lpbkit->lpfldrParent,
                            rgchName,
                            NULL,
                            &lpbkit->cbeidYr,
                            &lpbkit->lpeidYr,
                            &lpbkit->lpfldrYr);
            if (HR_FAILED (hr))
                goto ret;
        }
        else if (HR_FAILED (hr))
            goto ret;

        lpfldr = lpbkit->lpfldrYr;
    }
    else
        lpfldr = lpbkit->lpfldrParent;

    /* Cache the hashed target folder */

    hr = HrCacheHashedFolder (lpsmh,
                    ft,
                    &lpbkit->dft,
                    lpfldr,
                    &lpbkit->cbeid,
                    &lpbkit->lpeid,
                    &lpbkit->lpfldr,
                    &fUpdated);
    if (HR_FAILED (hr) && (GetScode (hr) == MAPI_E_NOT_FOUND))
    {
        if (!FileTimeToSystemTime (&ft, &st))
        {
            hr = ResultFromScode (MAPI_E_BAD_VALUE);
            goto ret;
        }
        if (fCatByYear)
        {
            wsprintf (rgchName,
                "%02hu %s",
                st.wMonth,
                rgtstrMonthFull[st.wMonth - 1]);
        }
        else
        {
            wsprintf (rgchName,
                TEXT("'%02hu/%02hu %s"),
                st.wYear % 100,
                st.wMonth,
                rgtstrMonthFull[st.wMonth - 1]);
        }
        st.wDayOfWeek = (st.wDay - st.wDayOfWeek - 1) % 7;
        st.wDay = 1;
        st.wHour = 0;
        st.wMinute = 0;
        st.wSecond = 0;
        st.wMilliseconds = 0;
        if (!SystemTimeToFileTime (&st, &lpbkit->dft.ftStart))
        {
            hr = ResultFromScode (MAPI_E_BAD_VALUE);
            goto ret;
        }
        st.wDay = rgwDaysPerMonth[st.wMonth - 1];
        if ((st.wMonth == 1) && FIsLeapYear (st.wYear))
            st.wDay += 1;
        st.wDayOfWeek = (st.wDayOfWeek + st.wDay - 1) % 7;
        st.wHour = 23;
        st.wMinute = 59;
        st.wSecond = 59;
        st.wMilliseconds = 999;
        if (!SystemTimeToFileTime (&st, &lpbkit->dft.ftEnd))
        {
            hr = ResultFromScode (MAPI_E_BAD_VALUE);
            goto ret;
        }
        hr = HrCreateHashedFolder (lpsmh,
                            &lpbkit->dft,
                            lpfldr,
                            rgchName,
                            NULL,
                            &lpbkit->cbeid,
                            &lpbkit->lpeid,
                            &lpbkit->lpfldr);
    }

ret:

    if (!HR_FAILED (hr))
    {
        LPBYTE lpeid;

        /* OK, If we get this far we are moving the message */

        hr = ResultFromScode ((*lpsmh->lpfnAlloc) (lpbkit->cbeid, &lpeid));
        if (HR_FAILED (hr))
            goto ret;
        memcpy (lpeid, lpbkit->lpeid, (UINT)lpbkit->cbeid);
        (*lpsmh->lpfnFree) (*lppeid);
        *lpcbeid = lpbkit->cbeid;
        *lppeid = lpeid;
    }
    (*lpsmh->lpfnFree) (lpval);
    lpval = NULL;

    DebugTraceResult (HrArchiveMessage(), hr);
    return hr;
}


/*
 *  HrFilterDeleted()
 *
 *  Purpose:
 *
 *      Filters all the current message from the 'Wastebasket'/'Deleted Items'
 *      folder based on the archiving model used in sent mail processing.
 *
 *  Arguments:
 *
 *      lpwb            wastbucket struct for the current store
 *      lpbin           sbinary holding the entryid of the message
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrFilterDeleted (LPWB lpwb, LPSBinary lpbin)
{
    HRESULT hr = ResultFromScode (MAPI_E_NOT_ENOUGH_MEMORY);
    LPBYTE lpeid = NULL;
    LPMAPIFOLDER lpfldr = lpwb->lpfldr;
    LPMDB lpmdb = lpwb->lpmdb;
    LPMESSAGE lpmsg = NULL;
    LPSMH lpsmh = lpwb->lpsmh;
    SBinaryArray sba = {1, 0};
    ULONG cb = lpwb->lpvalEid->Value.bin.cb;
    ULONG ulFlags = 0;
    ULONG ulType;

    sba.lpbin = lpbin;      // work around MS initializer

    if (!FAILED ((*lpsmh->lpfnAlloc) (cb, &lpeid)))
    {
        hr = lpmdb->lpVtbl->OpenEntry (lpmdb,
                                lpbin->cb,
                                (LPENTRYID)lpbin->lpb,
                                NULL,
                                0,
                                &ulType,
                                (LPUNKNOWN FAR *)&lpmsg);
        if (!HR_FAILED (hr))
        {
            memcpy (lpeid, lpwb->lpvalEid->Value.bin.lpb, (UINT)cb);
            hr = HrArchiveMessage (lpsmh,
                                lpmsg,
                                lpfldr,
                                lpmdb,
                                &lpwb->bkit,
                                lpsmh->fCatWb,
                                &cb,
                                &lpeid);
            if (!HR_FAILED (hr))
            {
                hr = lpfldr->lpVtbl->CopyMessages (lpfldr,
                                &sba,
                                NULL,
                                lpwb->bkit.lpfldr,
                                0,
                                NULL,
                                MAPI_MOVE);
            }
        }
    }
    (*lpsmh->lpfnFree) (lpeid);
    DebugTraceResult (HrFilterDeleted(), hr);
    return hr;
}


/*
 *  WBNotify()
 *
 *  Purpose:
 *
 *      Notification callback on the WB folders of message stores.  When
 *      rows are added to the WB contents table, we enum the table and
 *      filter each message added.
 *
 *  Arguments:
 *
 *      lpv         void pointer to current WB struct
 *      cntf        count of notifications
 *      lpntf       notifications
 *
 *  Returns:
 *
 *      (SCODE)
 */
STDAPI_(SCODE)
WBNotify (LPVOID lpv, ULONG cntf, LPNOTIFICATION lpntf)
{
    HRESULT hr;
    LPMAPITABLE lptbl;
    LPSMH lpsmh;
    LPSRowSet lprws = NULL;
    LPWB lpwb;
    UINT irw;

    /* Quick and dirty check on the context */

    if (IsBadReadPtr (lpv, sizeof(WB)) ||
        IsBadReadPtr (((LPWB)lpv)->lpsmh, sizeof(SMH)))
        return S_OK;

    lpwb = (LPWB)lpv;
    lptbl = lpwb->lptbl;
    lpsmh = lpwb->lpsmh;

    /* Just incase we were turned off */

    if (lpsmh->fCatWb)
    {
        while (cntf--)
        {
            Assert (lpntf->ulEventType == fnevTableModified);
            if (lpntf->info.tab.ulTableEvent == TABLE_ROW_ADDED)
            {
                lptbl->lpVtbl->SeekRow (lptbl, BOOKMARK_BEGINNING, 0, NULL);

                while (TRUE)
                {
                    hr = lptbl->lpVtbl->QueryRows (lptbl, 5, 0, &lprws);
                    if (HR_FAILED (hr))
                        break;

                    for (irw = 0; irw < lprws->cRows; irw++)
                    {
                        HrFilterDeleted (lpwb, &lprws->aRow[irw].lpProps[0].Value.bin);
                        (*lpsmh->lpfnFree) (lprws->aRow[irw].lpProps);
                    }

                    if (!lprws->cRows)
                        break;

                    (*lpsmh->lpfnFree) (lprws);
                    lprws = NULL;
                }
                break;
            }
            lpntf++;
        }
        (*lpsmh->lpfnFree) (lprws);
    }
    return S_OK;
}


/*
 *  HrInitDeletedMailFilter()
 *
 *  Purpose:
 *
 *      Inits the deleted mail filters by opening the store, finding the
 *      WB folder, opening the contents table of the WB, and registering
 *      for TABLE_CHANGED notifications.
 *
 *  Arguments:
 *
 *      lpsmg           the sample mail handler object
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrInitDeletedMailFilter (LPSMH lpsmh)
{
    HRESULT hr;
    LPMAPIADVISESINK lpadvz = NULL;
    LPMAPIFOLDER lpfldr = NULL;
    LPMAPITABLE lptbl = NULL;
    LPMDB lpmdb = NULL;
    LPSPropValue lpval = NULL;
    LPWB lpwb = NULL;
    SizedSPropTagArray (1, spt) = {1, { PR_ENTRYID }};
    ULONG ulType;
    UINT cerr = 0;
    UINT i;

    for (i = 0; i < lpsmh->lpstotbl->cSto; i++)
    {
        hr = ResultFromScode ((*lpsmh->lpfnAlloc) (sizeof(WB), &lpwb));
        if (HR_FAILED (hr))
            goto nxt;
        memset (lpwb, 0, sizeof(WB));

        hr = HrOpenStoEntry (lpsmh->lpsess, &lpsmh->lpstotbl->aSto[i], &lpmdb);
        if (HR_FAILED (hr))
            goto nxt;

        hr = HrGetOneProp ((LPMAPIPROP)lpmdb, PR_IPM_WASTEBASKET_ENTRYID, &lpval);
        if (HR_FAILED (hr))
            goto nxt;

        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))
            goto nxt;

        hr = lpfldr->lpVtbl->GetContentsTable (lpfldr, 0, &lptbl);
        if (HR_FAILED (hr))
            goto nxt;

        hr = lptbl->lpVtbl->SetColumns (lptbl, (LPSPropTagArray)&spt, 0);
        if (HR_FAILED (hr))
            goto nxt;

        hr = HrAllocAdviseSink ((LPNOTIFCALLBACK)&WBNotify, lpwb, &lpadvz);
        if (HR_FAILED (hr))
            goto nxt;

        hr = lptbl->lpVtbl->Advise (lptbl, fnevTableModified, lpadvz, &lpwb->ulAdvz);
        if (HR_FAILED (hr))
            goto nxt;

        UlAddRef (lptbl);
        UlAddRef (lpfldr);
        lpwb->lpmdb = lpmdb;
        lpwb->lptbl = lptbl;
        lpwb->lpfldr = lpfldr;
        lpwb->lpvalEid = lpval;
        lpwb->lpsmh = lpsmh;
        lpval = NULL;

        /* Hook it in */

        lpwb->wbNext = lpsmh->lstWb;
        lpsmh->lstWb = lpwb;
        lpwb = NULL;
nxt:

        if (HR_FAILED (hr))
            cerr++;

        (*lpsmh->lpfnFree) (lpval);
        lpval = NULL;

        (*lpsmh->lpfnFree) (lpwb);
        lpwb = NULL;

        UlRelease (lpadvz);
        lpadvz = NULL;

        UlRelease (lpfldr);
        lpfldr = NULL;

        UlRelease (lptbl);
        lptbl = NULL;
    }

    hr = ResultFromScode (cerr ? MAPI_W_ERRORS_RETURNED : S_OK);
    DebugTraceResult (HrInitDeletedMailFilter(), hr);
    return hr;
}


/*
 *  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++;
    }
    else
        return NULL;

    return (*lpsz ? lpsz : NULL);
}


/*
 *  FLpszContainsLpsz()
 *
 *  Purpose:
 *
 *      Finds the given sub-string in the passed in string.  This is a
 *      case insensitive matching model that normalizes the strings to
 *      upper case.  So, if preservation is required, pass in copies of
 *      the two strings
 *
 *  Arguments:
 *
 *      lpszSrc         source string
 *      lpsz            string to find
 *
 *  Returns:
 *
 *      FALSE iff the string was not found, TRUE otherwise.
 */
BOOL
FLpszContainsLpsz (LPTSTR lpszSrc, LPTSTR lpsz)
{
    UINT cch = lstrlen(lpsz);
    TCHAR ch;

    CharUpper (lpsz);
    CharUpper (lpszSrc);
    while (lpszSrc = LpszFindChar (lpszSrc, *lpsz))
    {
        if ((UINT)lstrlen(lpszSrc) < cch)
            break;

        ch = *(lpszSrc + cch);
        *(lpszSrc + cch) = 0;

        if (!lstrcmp (lpszSrc, lpsz))
            return TRUE;

        *(lpszSrc + cch) = ch;
        lpszSrc++;
    }
    return FALSE;
}


/*
 *  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;
    UINT isz = 0;
    LPSPropValue lpvalT = NULL;

    if (lpsmh->valEx.ulPropTag == PR_SMH_EXCLUSIONS)
    {
        if (!HR_FAILED (HrGetOneProp ((LPMAPIPROP)lpmsg, PR_MESSAGE_CLASS, &lpvalT)))
        {
            /* See if this is in the list of exclusions */

            for (isz = 0; isz < lpsmh->valEx.Value.MVSZ.cValues; isz++)
                if (!lstrcmpi (lpvalT->Value.LPSZ, lpsmh->valEx.Value.MVSZ.LPPSZ[isz]))
                    break;

            (*lpsmh->lpfnFree) (lpvalT);
        }
        sc = ((isz == lpsmh->valEx.Value.MVSZ.cValues)
                    ? MAPI_E_NOT_ME
                    : S_OK);
    }
    else
        sc = 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)
    {
        if ((lprl->rlTyp == RL_FROM) ||
            (lprl->rlTyp == RL_ATTACH) ||
            (lprl->rlTyp == RL_SUBJECT))
        {
            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) ||
        (lprl->rlTyp == RL_CC) ||
        (lprl->rlTyp == RL_BCC) ||
        (lprl->rlTyp == RL_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[ipSubj].ulPropTag == PR_SUBJECT)
        {
            if (FLpszContainsLpsz (lpval[ipSubj].Value.LPSZ, lprl->lpszData))
                hr = hrSuccess;
        }
    }
    else if (lprl->rlTyp == RL_FROM)
    {
        if (lpval[ipSentRep].ulPropTag == PR_SENT_REPRESENTING_NAME)
        {
            if (FLpszContainsLpsz (lpval[ipSentRep].Value.LPSZ, lprl->lpszData))
                hr = hrSuccess;
        }
        if (HR_FAILED (hr))
        {
            if (lpval[ipSentRepEA].ulPropTag == PR_SENT_REPRESENTING_EMAIL_ADDRESS)
            {
                if (FLpszContainsLpsz (lpval[ipSentRepEA].Value.LPSZ, lprl->lpszData))
                    hr = hrSuccess;
            }
        }
    }
    else if (lprl->rlTyp == RL_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_CLASS)
    {
        if (!HR_FAILED (HrGetOneProp ((LPMAPIPROP)lpmsg, PR_MESSAGE_CLASS, &lpvalT)))
        {
            if (FLpszContainsLpsz (lpvalT->Value.LPSZ, lprl->lpszData))
                hr = hrSuccess;

            (*lpsmh->lpfnFree) (lpvalT);
        }
    }

    if (HR_FAILED (hr))
    {
        Assert (GetScode (hr) == MAPI_E_NOT_ME);
        if (lprl->ulFlags & RULE_NOT)
            hr = hrSuccess;
    }

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;

    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[ipType].ulPropTag != PR_RULE_TYPE) ||
        (lpval[ipData].ulPropTag != PR_RULE_DATA) ||
        (lpval[ipRLFlags].ulPropTag != PR_RULE_FLAGS) ||
        (!(lpval[ipRLFlags].Value.l & RULE_DELETE) &&
          (lpval[ipType].ulPropTag != PR_RULE_TYPE) ||
          (lpval[ipPath].ulPropTag != PR_RULE_TARGET_PATH) ||
          (lpval[ipStore].ulPropTag != PR_RULE_STORE_DISPLAY_NAME)))
    {
        /* Something very important is missing */

        hr = ResultFromScode (MAPI_E_UNCONFIGURED);
        goto ret;
    }
    lprl->rlTyp = (UINT)lpval[ipType].Value.l;
    lprl->ulFlags = lpval[ipRLFlags].Value.l;
    memcpy (lprl->lpszData, lpval[ipData].Value.bin.lpb, (UINT)lpval[ipData].Value.bin.cb);

    if (lpval[ipSound].ulPropTag == PR_RULE_SOUND_FILENAME)
        lstrcpy (lprl->lpszSound, lpval[ipSound].Value.lpszA);

    if (!(lpval[ipRLFlags].Value.l & RULE_DELETE))
    {
        hr = HrOpenMdbFromName (lpsmh, lpval[ipStore].Value.LPSZ, &lprl->lpmdb);
        if (HR_FAILED (hr))
            goto ret;

        if ((lpval[ipEID].ulPropTag != PR_RULE_TARGET_ENTRYID) ||
            (HR_FAILED (lpsess->lpVtbl->OpenEntry (lpsess,
                            lpval[ipEID].Value.bin.cb,
                            (LPENTRYID)lpval[ipEID].Value.bin.lpb,
                            NULL,
                            MAPI_MODIFY,
                            &ulType,
                            (LPUNKNOWN FAR *)&lprl->lpfldr))))
        {
            hr = HrFolderFromPath (lpsmh,
                            lprl->lpmdb,
                            lpval[ipPath].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[ipType].Value.l == RL_TO) ||
        (lpval[ipType].Value.l == RL_CC) ||
        (lpval[ipType].Value.l == RL_BCC) ||
        (lpval[ipType].Value.l == RL_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[ipType].Value.l == RL_TO)
                                    ? MAPI_TO
                                    : ((lpval[ipType].Value.l == RL_CC)
                                        ? MAPI_CC
                                        : ((lpval[ipType].Value.l == RL_BCC)
                                            ? 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[ipType].Value.l == RL_TO) ||
            (lpval[ipType].Value.l == RL_CC) ||
            (lpval[ipType].Value.l == RL_BCC))
            lprl->lpres = &lpres[iresAnd];
        else
            lprl->lpres = &lpres[iresOr];
    }

ret:

    (*lpsmh->lpfnFree) (lpval);

    UlRelease (lpprof);
    if (HR_FAILED (hr))
    {
        (*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;
}


/*
 *  CleanupSMH()
 *
 *  Purpose:
 *
 *      Cleans up all resources and objects held by the SMH object.
 *
 *  Arguments:
 *
 *      lpsmh           the SMH parent object
 */
VOID
CleanupSMH (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);
        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);

    }
    lpsmh->lstRl = NULL;
    ReleaseStoresTable (lpsmh);
}


/*
 *  HrInitSMH()
 *
 *  Purpose:
 *
 *      Initializes the SMH stucture, loads all filters, loads all
 *      exclusions, creates any unread search folders, and inits all
 *      wastebasket objects -- all based on the profile section.
 *
 *  Arguments:
 *
 *      lpsmh           the SMH parent object
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrInitSMH (LPSMH lpsmh)
{
    HRESULT hr;
    LPMAPISESSION lpsess = lpsmh->lpsess;
    LPMAPIUID lpmuid = &lpsmh->muid;
    LPPROFSECT lpprof = NULL;
    LPSPropValue lpval = NULL;
    ULONG cval;

    /* Get options from the profile */

    hr = lpsess->lpVtbl->OpenProfileSection (lpsess,
                            lpmuid,
                            NULL,
                            MAPI_MODIFY,
                            &lpprof);
    if (HR_FAILED (hr))
        goto ret;

    hr = lpprof->lpVtbl->GetProps (lpprof,
                            (LPSPropTagArray)&sptConfigProps,
                            0,
                            &cval,
                            &lpval);
    UlRelease (lpprof);
    if (HR_FAILED (hr))
        goto ret;

    /* Check to see if we are configured */

    if (REQ_PROPS != (UlChkReqProps (cval, lpval) & REQ_PROPS))
    {
        hr = ResultFromScode (MAPI_E_UNCONFIGURED);
        goto ret;
    }

    /* 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, lpsess);
    if (HR_FAILED (hr))
        goto ret;

    /* Store the values */

    lpsmh->fCatSm = !!(lpval[ipSMHFlags].Value.l & SMH_FILTER_SENTMAIL);
    lpsmh->fCatSmByYr = !!(lpval[ipSMHFlags].Value.l & SMH_FILTER_SENTMAIL_YR);
    if (lpval[ipSMHFlags].Value.l & SMH_FILTER_DELETED)
    {
        HrInitDeletedMailFilter (lpsmh);
        lpsmh->fCatWb = !!(lpval[ipSMHFlags].Value.l & SMH_FILTER_DELETED_YR);
    }
    if (!!(lpval[ipSMHFlags].Value.l & SMH_UNREAD_VIEWER))
        HrInitUnreadSearch (lpsmh);

    if ((lpval[ipSMHFlags].Value.l & SMH_FILTER_INBOUND) &&
        (lpval[ipRules].ulPropTag == PR_SMH_RULES) &&
        (lpval[ipRules].Value.bin.cb != 0))
    {
        UINT crl = (UINT)(lpval[ipRules].Value.bin.cb / sizeof(MAPIUID));
        LPMAPIUID lpmuid = ((LPMAPIUID)lpval[ipRules].Value.bin.lpb) + crl;
        LPRULE lprl;

        while (crl--)
        {
            --lpmuid;
            hr = HrBuildRule (lpsmh, lpmuid, &lprl);
            if (!HR_FAILED (hr))
            {
                lprl->rlNext = lpsmh->lstRl;
                lpsmh->lstRl = lprl;
            }
        }
        hr = hrSuccess;
    }

ret:

    (*lpsmh->lpfnFree) (lpval);
    DebugTraceResult (HrInitSMH(), hr);
    return hr;
}


/*
 *  HrReConfigSMH()
 *
 *  Purpose:
 *
 *      Cleans up the current SMH configuration and reloads it from
 *      scratch.
 *
 *  Arguments:
 *
 *      lpsmh           the SMH parent object
 *
 *  Returns:
 *
 *      (HRESULT)
 */
HRESULT
HrReConfigSMH (LPSMH lpsmh)
{
    HRESULT hr;

    CleanupSMH (lpsmh);
    hr = HrInitSMH (lpsmh);
    if (!HR_FAILED (hr))
        ResetConfigEvent (lpsmh->hevtConfig);

    DebugTraceResult (HrReconfigSMH(), hr);
    return hr;
}


/*
 *  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;

    CloseConfigEvent (lpsmh->hevtConfig);
    CleanupSMH (lpsmh);
    UlRelease (lpsmh->lpsess);
    (*lpsmh->lpfnFree) (lpsmh);
    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);

    if (FConfigChanged (lpsmh->hevtConfig) &&
        HR_FAILED (hr = HrReConfigSMH (lpsmh)))
    {
        DebugTraceResult (SMH_InboundMsgHook(), hr);
        return hrSuccess;
    }

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

                        if (lstrlen (lprl->lpszSound))
                            sndPlaySound (lprl->lpszSound, SND_ASYNC);
                    }
                    else
                        *lpulFlags = HOOK_DELETE | HOOK_CANCEL;

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

    (*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);

    if (FConfigChanged (lpsmh->hevtConfig) &&
        HR_FAILED (hr = HrReConfigSMH (lpsmh)))
    {
        DebugTraceResult (SMH_InboundMsgHook(), hr);
        return hrSuccess;
    }

    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;
    HRESULT hr;
    LPSMH lpsmh = NULL;

    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))
    {
        (*lpfnFree) (lpsmh);
        lpsmh = NULL;
        goto ret;
    }

    hr = HrGetConfigEvent (&lpsmh->hevtConfig);
    if (HR_FAILED (hr))
    {
        (*lpfnFree) (lpsmh);
        lpsmh = NULL;
        goto ret;
    }

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

    hr = HrInitSMH (lpsmh);

ret:

    if (HR_FAILED (hr))
    {
        UlRelease (lpsmh);
        lpsmh = NULL;
    }
    *lppHook = (LPSPOOLERHOOK)lpsmh;
    DebugTraceResult (SMH_Init(), hr);
    return hr;
}
