XPSNDMSG.C
/* 
 -  X P S N D M S G . C 
 - 
 *  Purpose: 
 *      Code to support the MAPI Transport SPI entry points for 
 *      message transmission. 
 *      This module contains the following SPI entry points: 
 * 
 *          SubmitMessage() 
 *          EndMessage() 
 * 
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved. 
 */ 
 
#include "xppch.h" 
 
 
HRESULT HrAddDeferred(LPXPL lpxpl, LPMESSAGE lpMsg, ULONG *lpulMsgRef); 
 
 
/* 
 -  XPL_SubmitMessage 
 - 
 *  Purpose: 
 *      Called by the Spooler when it wishes to attempt transmission 
 *      of a message. 
 * 
 *  Parameters: 
 *      ulFlags             Flags from the Spooler. The only 
 *                          flag defined in the MAPI 1.0 TSPI 
 *                          is BEGIN_DEFERRED, and this transport 
 *                          doesn't do deferred transmission. 
 *      lpMessage           Pointer to message object that the 
 *                          Spooler wants sent. 
 *      lpulMsgRef          Pointer to where the transport should 
 *                          store a unsigned long for use in 
 *                          identifying TransportNotify() message 
 *                          events. Initialized to 1 by the 
 *                          Spooler. We use this to ID deferred 
 *                          messages using DEFERRED_MSG_REF. 
 *      lpulReturnParm      Used for several MAPI_E_XXX returns, 
 *                          but this transport doesn't do any 
 *                          of them. 
 * 
 *  Returns: 
 *      (HRESULT)           MAPI_E_BUSY if the Spooler calls 
 *                          here again while we're busy, else 
 *                          errors encountered if any. 
 * 
 *  Operation: 
 *      For non peer-to-peer, "send" the message to our outbound directory. 
 * 
 *      For peer-to-peer, use the ADRLIST builder HrBuildAdrList to attempt 
 *      transmission to each recipient and build both a sent ADRLIST and a 
 *      NDR ADRLIST. ModifyRecipients() on the Spooler message if there's 
 *      anyone in the sent ADRLIST. This tells the spooler which ones got 
 *      the mail.  If there were any recipients in the NDR ADRLIST, we need 
 *      to StatusRecips() with that list, and we will need to 
 *      ModifyRecipients() again with them. 
 * 
 *      For non peer-to-peer or p2p with NDR recipients, use the ADRLIST 
 *      builder HrBuildAdrList to make a ADRLIST with all of our unsent 
 *      recipients (for non p2p, delivery to all recipients consists of 
 *      putting a file into the outbound and for NDR cases, we just take 
 *      responsibility because we've already NDR'ed them) and 
 *      ModifyRecipients(). 
 */ 
 
STDMETHODIMP 
XPL_SubmitMessage(LPXPL lpxpl, 
    ULONG ulFlags, 
    LPMESSAGE lpMessage, 
    ULONG * lpulMsgRef, 
    ULONG * lpulReturnParm) 
{ 
    LPTSTR lpszMyDir; 
    HRESULT hResult = 0; 
    SCODE sc = 0; 
    BOOL fUpdatedStatus = FALSE; 
    BOOL fPeer2Peer; 
 
    LPSPropValue lpPropArray = NULL; 
    LPMAPISUP lpMAPISup; 
 
    ULONG cValues; 
    LPSPropValue lpMsgProps = NULL; 
 
    static const SizedSPropTagArray(2, sptMsgDefer) = 
    { 
        2, 
        { 
            PR_SAMPLE_PER_MSG_DEFER, 
            PR_SAMPLE_PER_RECIP_DEFER 
        } 
    }; 
 
    SPropValue spvRecipUnsent = 
    {PR_RESPONSIBILITY, 0L, FALSE}; 
    SPropValue spvRecipDefer = 
    {PR_SAMPLE_PER_RECIP_DEFER, 0L, TRUE}; 
    SRestriction rgsrOr[2]; 
    SRestriction rgsrAnd[2]; 
    SRestriction srExist; 
    SRestriction srRecipUnsent; 
 
    LPMAPITABLE lpTable = NULL; 
    LPMYADRLIST lpMyAdrListDone = NULL; 
    LPMYADRLIST lpMyAdrListNotDone = NULL; 
 
    /* Simple re-entrancy test. Should never happen anyway. */ 
 
    if (lpxpl->ulTransportStatus & STATUS_OUTBOUND_ACTIVE) 
    { 
        hResult = ResultFromScode(MAPI_E_BUSY); 
        DebugTrace("XPL_SubmitMessage reentrancy test failed.\n"); 
        goto ret; 
    } 
 
    /* Signal that we're uploading. */ 
 
    lpxpl->ulTransportStatus |= STATUS_OUTBOUND_ACTIVE; 
    hResult = HrUpdateTransportStatus(lpxpl, 0L); 
    if (hResult) 
        goto ret; 
    fUpdatedStatus = TRUE; 
 
    /*  Session's OK, hook up some local references */ 
 
    sc = ScCopySessionProps(lpxpl, &lpPropArray, NULL); 
 
    if (FAILED(sc)) 
    { 
        hResult = ResultFromScode(sc); 
        DebugTrace("ScCopySessionProps failed in SubmitMessage.\n"); 
        goto ret; 
    } 
 
    lpMAPISup = lpxpl->lpMAPISup; 
    fPeer2Peer = ((ArrayIndex(PR_SAMPLE_FLAGS, lpPropArray).Value.ul) & PR_SAMPLE_FLAG_PEER_TO_PEER) != 0; 
    lpszMyDir = ArrayIndex(PR_SAMPLE_OUTBOUND_DIR, lpPropArray).Value.LPSZ; 
 
    PrintfTransportLog(TEXT("Start Outbound: %s"), 
        ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ); 
 
    /* Reset the timer for SpoolerYield() */ 
 
    HrCheckSpoolerYield(lpMAPISup, TRUE); 
 
    /* Check if we are to defer this message */ 
 
    hResult = lpMessage->lpVtbl->GetProps(lpMessage, 
        (LPSPropTagArray) &sptMsgDefer, 0, /* ansi */ 
        &cValues, &lpMsgProps); 
 
    if (HR_FAILED(hResult)) 
    { 
        DebugTrace("Failed getting props on the message.\n"); 
        goto ret; 
    } 
 
    /* We'll default fResendDeferred to TRUE unless we defer this msg */ 
 
    lpxpl->fResendDeferred = TRUE; 
 
    /* If PR_SAMPLE_PER_MSG_DEFER exists and is TRUE, then we delete 
       this property from the message, and add this message to our 
       deferred list.  The lpulMsgRef is set to the value assigned to 
       the node in our list.  When EndMessage is called we search for 
       this Ref in to list.  If found, we return END_DONT_RESEND, 
       causing the spooler to defer the msg. */ 
 
    if (lpMsgProps->ulPropTag == PR_SAMPLE_PER_MSG_DEFER) 
    { 
        lpMessage->lpVtbl->DeleteProps(lpMessage, 
            (LPSPropTagArray) &sptMsgDefer, NULL); 
 
        lpMessage->lpVtbl->SaveChanges(lpMessage, KEEP_OPEN_READWRITE); 
 
        if (lpMsgProps->Value.b == TRUE) 
        { 
            hResult = HrAddDeferred(lpxpl, lpMessage, lpulMsgRef); 
            goto ret; 
        } 
    } 
 
    /* Now, see if any Recipients have PR_SAMPLE_PER_RECIP_DEFER set to 
       TRUE in the RecipientTable.  If so, then slam a non-transmittable 
       property into the message to indicate we've deferred this message 
       for some recipients this time around.  If this property already 
       exists, then we know it has already been deferred and we should 
       just go ahead and send to all un-handled recipients.              */ 
 
    if (lpMsgProps[1].ulPropTag == PR_SAMPLE_PER_RECIP_DEFER) 
    { 
        lpMessage->lpVtbl->DeleteProps(lpMessage, 
            (LPSPropTagArray) &sptMsgDefer, NULL); 
 
        lpMessage->lpVtbl->SaveChanges(lpMessage, KEEP_OPEN_READWRITE); 
 
        /*  Initialize a restriction -- we'll need it soon. */ 
 
        srRecipUnsent.rt = RES_PROPERTY; 
        srRecipUnsent.res.resProperty.relop = RELOP_EQ; 
        srRecipUnsent.res.resProperty.ulPropTag = PR_RESPONSIBILITY; 
        srRecipUnsent.res.resProperty.lpProp = &spvRecipUnsent; 
    } 
    else 
    { 
        SRestriction srRecipDefer; 
 
        srRecipDefer.rt = RES_PROPERTY; 
        srRecipDefer.res.resProperty.relop = RELOP_EQ; 
        srRecipDefer.res.resProperty.ulPropTag = PR_SAMPLE_PER_RECIP_DEFER; 
        srRecipDefer.res.resProperty.lpProp = &spvRecipDefer; 
 
        hResult = lpMessage->lpVtbl->GetRecipientTable( 
            lpMessage, 0L, &lpTable); 
 
        if (hResult) 
        { 
            DebugTrace("GetRecipientTable failed in SubmitMessage.\n"); 
            goto ret; 
        } 
 
        hResult = lpTable->lpVtbl->FindRow(lpTable, &srRecipDefer, 
            BOOKMARK_BEGINNING, 0); 
 
        /* Check our .2 second timer! */ 
 
        sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE)); 
 
        if (sc == MAPI_W_CANCEL_MESSAGE) 
        { 
            DebugTrace("Cancelling message delivery.\n"); 
            goto ret; 
        } 
 
        if (GetScode(hResult) != MAPI_E_NOT_FOUND) 
        { 
            hResult = HrAddDeferred(lpxpl, lpMessage, lpulMsgRef); 
 
            if (HR_FAILED(hResult)) 
            { 
                DebugTrace("HrAddDeferred failed doing a per-recip deferral.\n"); 
                goto ret; 
            } 
        } 
        else 
        { 
            hResult = lpTable->lpVtbl->SeekRow(lpTable, 
                BOOKMARK_BEGINNING, 0L, NULL); 
        } 
 
        /*  Initialize a restriction -- we'll need it soon. */ 
 
        spvRecipDefer.Value.b = FALSE; 
 
        srExist.rt = RES_EXIST; 
        srExist.res.resExist.ulPropTag = PR_SAMPLE_PER_RECIP_DEFER; 
 
        rgsrOr[0].rt = RES_NOT; 
        rgsrOr[0].res.resNot.lpRes = &srExist; 
 
        rgsrOr[1].rt = RES_PROPERTY; 
        rgsrOr[1].res.resProperty.relop = RELOP_EQ; 
        rgsrOr[1].res.resProperty.ulPropTag = PR_SAMPLE_PER_RECIP_DEFER; 
        rgsrOr[1].res.resProperty.lpProp = &spvRecipDefer; 
 
        rgsrAnd[0].rt = RES_PROPERTY; 
        rgsrAnd[0].res.resProperty.relop = RELOP_EQ; 
        rgsrAnd[0].res.resProperty.ulPropTag = PR_RESPONSIBILITY; 
        rgsrAnd[0].res.resProperty.lpProp = &spvRecipUnsent; 
 
        rgsrAnd[1].rt = RES_OR; 
        rgsrAnd[1].res.resOr.cRes = 2; 
        rgsrAnd[1].res.resOr.lpRes = rgsrOr; 
 
        srRecipUnsent.rt = RES_AND; 
        srRecipUnsent.res.resAnd.cRes = 2; 
        srRecipUnsent.res.resAnd.lpRes = rgsrAnd; 
    } 
 
    /* Check our .2 second timer before attempting to send */ 
 
    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE)); 
 
    if (sc == MAPI_W_CANCEL_MESSAGE) 
    { 
        DebugTrace("Cancelling message delivery.\n"); 
        goto ret; 
    } 
 
    /*  If not peer-to-peer, "send message" to our outbound directory. */ 
 
    if (!fPeer2Peer) 
    { 
        BOOL fSent; 
 
        hResult = HrSendOneMessage(lpxpl, lpPropArray, 
                lpMessage, 0, lpszMyDir, &fSent); 
 
        if (hResult) 
        { 
            DebugTrace("Copying message to outbound failed.\n"); 
            goto ret; 
        } 
    } 
 
    /*  If we are peer-to-peer, send to all our recipients. */ 
 
    else 
    { 
        /*  Get the recipient table from the message if we haven't already */ 
 
        if (!lpTable) 
        { 
            hResult = lpMessage->lpVtbl->GetRecipientTable( 
                lpMessage, 0L, &lpTable); 
 
            if (hResult) 
            { 
                DebugTrace("GetRecipientTable failed in SubmitMessage.\n"); 
                goto ret; 
            } 
        } 
 
        /*  Restrict to all unsent recipients */ 
 
        hResult = lpTable->lpVtbl->Restrict(lpTable, &srRecipUnsent, 0L); 
 
        if (hResult) 
        { 
            DebugTrace("Restriction on recipient table  failed in SubmitMessage.\n"); 
            goto ret; 
        } 
 
        /* Check our .2 second timer! */ 
 
        sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE)); 
 
        if (sc == MAPI_W_CANCEL_MESSAGE) 
        { 
            DebugTrace("Cancelling message delivery.\n"); 
            goto ret; 
        } 
 
        /*  Send to the recipients which we can reach, and build ADRLISTs of 
            the sent and unsent recipients */ 
 
        hResult = HrBuildAdrList(lpxpl, lpPropArray, lpMessage, lpTable, 
                TRUE, HrSendOneMessage, &lpMyAdrListDone, &lpMyAdrListNotDone); 
 
        if (hResult) 
        { 
            DebugTrace("HrBuildAdrList failed in SubmitMessage.\n"); 
            goto ret; 
        } 
 
        /*  No error, do we have some recipients? If so, do the ModifyRecipients(). */ 
 
        if (lpMyAdrListDone) 
        { 
            hResult = lpMessage->lpVtbl->ModifyRecipients(lpMessage, 
                    MODRECIP_MODIFY, lpMyAdrListDone->lpAdrList); 
 
            if (hResult) 
            { 
                DebugTrace("ModifyRecipients failed in SubmitMessage.\n"); 
                goto ret; 
            } 
 
            /* Now we need to save changes on the message. */ 
 
            hResult = lpMessage->lpVtbl->SaveChanges(lpMessage, 
                    lpMyAdrListNotDone ? KEEP_OPEN_READWRITE : 0L); 
 
            if (hResult) 
            { 
                DebugTrace("SaveChanges failed in SubmitMessage.\n"); 
                goto ret; 
            } 
        } 
 
        /*  Check for unsent recipients. If there were any, we need to NDR 
            them and (finally) mark them as taken. If there were not any, 
            we're really finished and can just go and release the message. */ 
 
        if (!lpMyAdrListNotDone) 
            goto ret; 
 
        hResult = lpMAPISup->lpVtbl->StatusRecips(lpMAPISup, 
                lpMessage, lpMyAdrListNotDone->lpAdrList); 
 
        if (HR_FAILED(hResult)) 
        { 
            DebugTrace("StatusRecips failed in SubmitMessage.\n"); 
            goto ret; 
        } 
 
        /*  StatusRecips killed the ADRLIST so zero out our pointer. */ 
 
        lpMyAdrListNotDone->lpAdrList = NULL; 
 
        /* Get rid of the MYADRLISTs. */ 
 
        FreeMyAdrList(lpxpl, lpMyAdrListDone); 
        FreeMyAdrList(lpxpl, lpMyAdrListNotDone); 
 
        lpMyAdrListDone = lpMyAdrListNotDone = NULL; 
 
        /*  Free the recipient table. */ 
 
        lpTable->lpVtbl->Release(lpTable); 
        lpTable = NULL; 
    } 
 
    /* Check our .2 second timer after attempting to send */ 
 
    sc = GetScode(HrCheckSpoolerYield(lpMAPISup, FALSE)); 
 
    if (sc == MAPI_W_CANCEL_MESSAGE) 
    { 
        DebugTrace("Cancelling message delivery.\n"); 
        goto ret; 
    } 
 
    /*  At this point we have either: 
 
        a)  If not peer-to-peer, made a single container file in 
            the outbound directory 
        b)  If peer-to-peer, created a container file in every 
            inbound directory we were able to reach (but not all 
            of them) -- and have NDR'ed the ones we could not reach 
 
        All that remains to do is to mark all unsent recipients as 
        handled, since in the non-peer case the creation of the 
        container suffices as "handling" and in the peer case the 
        NDR message also does this. 
    */ 
 
    hResult = lpMessage->lpVtbl->GetRecipientTable(lpMessage, 0L, &lpTable); 
 
    if (hResult) 
    { 
        DebugTrace("Second GetRecipientTable failed in SubmitMessage.\n"); 
        goto ret; 
    } 
 
    /*  Restrict the recipient table to the remaining unsent */ 
 
    hResult = lpTable->lpVtbl->Restrict(lpTable, &srRecipUnsent, 0L); 
 
    if (hResult) 
    { 
        DebugTrace("Restriction on recipient table failed.\n"); 
        goto ret; 
    } 
 
    /*  Build the ADRLIST of the unsent recipients */ 
 
    hResult = HrBuildAdrList(lpxpl, lpPropArray, lpMessage, lpTable, 
            TRUE, NULL,  &lpMyAdrListDone, &lpMyAdrListNotDone); 
 
    if (hResult) 
    { 
        DebugTrace("HrBuildAdrList failed in SubmitMessage.\n"); 
        goto ret; 
    } 
 
    Assert(!lpMyAdrListNotDone); 
 
    /*  No error, do we have some recipients? If so, do the ModifyRecipients(). */ 
 
    if (lpMyAdrListDone) 
    { 
        hResult = lpMessage->lpVtbl->ModifyRecipients(lpMessage, 
                MODRECIP_MODIFY, lpMyAdrListDone->lpAdrList); 
 
        if (hResult) 
        { 
            DebugTrace("ModifyRecipients failed in SubmitMessage.\n"); 
            goto ret; 
        } 
    } 
 
    /*  Release the table, we're finished with it */ 
 
    UlRelease(lpTable); 
    lpTable = NULL; 
 
    /* With the recipient table work done, save changes again. */ 
 
    hResult = lpMessage->lpVtbl->SaveChanges(lpMessage, 0L); 
 
    if (hResult) 
    { 
        DebugTrace("SaveChanges failed in SubmitMessage.\n"); 
        goto ret; 
    } 
 
    PrintfTransportLog(TEXT("End Outbound: %s"), 
        ArrayIndex(PR_SAMPLE_DISPLAY_NAME, lpPropArray).Value.LPSZ); 
 
ret: 
    /* Free-Up Memory */ 
 
    lpxpl->FreeBuffer(lpPropArray); 
    lpxpl->FreeBuffer(lpMsgProps); 
 
    /* Get rid of any MYADRLISTs. */ 
 
    FreeMyAdrList(lpxpl, lpMyAdrListDone); 
    FreeMyAdrList(lpxpl, lpMyAdrListNotDone); 
 
    /* Release any open table */ 
 
    UlRelease(lpTable); 
 
    /* Release the spooler's message if need be */ 
 
    UlRelease(lpMessage); 
 
    /* Reset upload status if set. If this errors, use the error only 
       if no other error had occurred here. */ 
 
    if (fUpdatedStatus) 
    { 
        lpxpl->ulTransportStatus &= ~STATUS_OUTBOUND_ACTIVE; 
        (void)HrUpdateTransportStatus(lpxpl, 0L); 
    } 
 
    DebugTraceResult(XPL_SubmitMessage, hResult); 
    return hResult; 
 
} 
 
/* 
 -  XPL_EndMessage 
 - 
 *  Purpose: 
 *      Called by the Spooler to complete transmission of a message. 
 * 
 *  Parameters: 
 *      ulMsgRef            Opaque identifier from SubmitMessage 
 *      lpulFlags           Pointer to where the transport should 
 *                          store special flags on return. This 
 *                          Transport doesn't use any. 
 * 
 *  Returns: 
 *      (HRESULT)           Success. 
 * 
 *  Operation: 
 *      Clears unsigned long status, and returns success. 
 */ 
 
STDMETHODIMP 
XPL_EndMessage(LPXPL lpxpl, ULONG ulMsgRef, ULONG * lpulFlags) 
{ 
    LPDEFMSG lpDefMsg; 
 
    Assert(!IsBadWritePtr(lpulFlags, sizeof(ULONG))); 
 
    *lpulFlags = 0L; 
 
    /* If the ulMsgRef is non zero, look for it in the deferred 
       list.  If found, return END_DONT_RESEND to the Spooler.  */ 
 
    if (ulMsgRef) 
    { 
        lpDefMsg = lpxpl->lpDeferredList; 
 
        while (lpDefMsg) 
        { 
            if (lpDefMsg->ulMsgRef == ulMsgRef) 
            { 
                *lpulFlags = END_DONT_RESEND; 
                break; 
            } 
        } 
    } 
 
    return hrSuccess; 
} 
 
 
/* 
 -  HrAddDeferred 
 - 
 *  Purpose: 
 *      Adds the EntryID and ulMsgRef of a message we are deferring 
 *      to our list of deferred message. 
 * 
 *  Parameters: 
 *      lpxpl           Transport session 
 *      lpMsg           The message we wish to defer 
 *      lpulMsgRef      Receives the reference we assign to this message 
 * 
 *  Returns: 
 *      hResult         Indicating Success/Failure 
 */ 
 
HRESULT 
HrAddDeferred(LPXPL lpxpl, LPMESSAGE lpMsg, ULONG *lpulMsgRef) 
{ 
    HRESULT hResult; 
    SCODE sc; 
    LPDEFMSG lpDefMsg = NULL; 
    ULONG cVals; 
    LPSPropValue lpEIDProp = NULL; 
    static SPropTagArray spta = {1, {PR_ENTRYID}}; 
 
    /* Allocate a new DefMsg node */ 
 
    sc = lpxpl->AllocateBuffer(sizeof(DEFMSG), (LPVOID *)&lpDefMsg); 
 
    if (FAILED(sc)) 
    { 
        hResult = ResultFromScode(sc); 
        DebugTrace("Allocation failed in HrAddDeferred.\n"); 
        goto ret; 
    } 
 
    /* Get the EntryID of the message we are deferring */ 
 
    hResult = lpMsg->lpVtbl->GetProps(lpMsg, &spta, 0, &cVals, &lpEIDProp); 
 
    if (HR_FAILED(hResult)) 
    { 
        DebugTrace("GetProps failed in HrAddDeferred.\n"); 
        goto ret; 
    } 
 
    Assert(cVals == 1); 
    Assert(lpEIDProp); 
    Assert(lpEIDProp->ulPropTag == PR_ENTRYID); 
 
    /* We'll make our own copy of the EntryID */ 
 
    sc = lpxpl->AllocateMore(lpEIDProp->Value.bin.cb, (LPVOID)lpDefMsg, 
            (LPVOID *)&(lpDefMsg->sbinEIDDef.lpb)); 
 
    if (FAILED(sc)) 
    { 
        hResult = ResultFromScode(sc); 
        DebugTrace("Allocation failed in HrAddDeferred.\n"); 
        goto ret; 
    } 
 
    /* Fill in this new node and add it to the list */ 
 
    lpDefMsg->sbinEIDDef.cb = lpEIDProp->Value.bin.cb; 
    memcpy(lpDefMsg->sbinEIDDef.lpb, lpEIDProp->Value.bin.lpb, 
            (UINT)lpDefMsg->sbinEIDDef.cb); 
 
    lpDefMsg->ulMsgRef = ++lpxpl->ulDeferredMsgRef; 
    lpDefMsg->lpNext = lpxpl->lpDeferredList; 
    lpxpl->lpDeferredList = lpDefMsg; 
 
    lpxpl->fResendDeferred = FALSE; 
 
    *lpulMsgRef = lpDefMsg->ulMsgRef; 
 
ret: 
    if (HR_FAILED(hResult)) 
        lpxpl->FreeBuffer(lpDefMsg); 
 
    lpxpl->FreeBuffer(lpEIDProp); 
 
    DebugTraceResult(HrAddDeferred, hResult); 
    return hResult; 
}