COMMANDS.C

// --Commands.c----------------------------------------------------------------- 
//
// This module contains SMBAGENT's command table and the handling procedure for
// each of SMBAGENT's commands.
//
// New Create a new topic with an introduction.
// Join Join a topic as a subscriber.
// Post Post a message to a topic and broadcast it to all members.
// Intro Return the introductory message of a topic to the subscriber.
// Leave Remove a subscriber from the topic.
// Topics List all topics placing "***" before those you subscribe to.
// Members List all members of a topic.
//
// Copyright (C) Microsoft Corp. 1986-1996. All Rights Reserved.
// -----------------------------------------------------------------------------

#include "edk.h"
#include <ctype.h>
#include "commands.chk"

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

#define UNINITIALIZED -1L // flag for uninitalized property tags

// Command handling procedure declarations
static HRESULT HrParseCommand( LPMESSAGE);
static VOID DoneWithMessage();

static HRESULT HrProcessNew( LPMESSAGE);
static HRESULT HrProcessJoin( LPMESSAGE);
static HRESULT HrProcessPost( LPMESSAGE);
static HRESULT HrProcessIntro( LPMESSAGE);
static HRESULT HrProcessLeave( LPMESSAGE);
static HRESULT HrProcessTopics( LPMESSAGE);
static HRESULT HrProcessMembers( LPMESSAGE);

// Command table definition
typedef struct
{
ULONG ulCmdRID; // Resource ID for command string
TCHAR szCmdName[MAXCMDLEN]; // Command name string
HRESULT (*lpfnCmdProc) // Command handling procedure
(LPMESSAGE lpMessage); // Entry ID and size for message
} CMDENTRY; // Command entry

static CMDENTRY aCmdTable[] =
{
{ IDS_CMD_NEW, TEXT(""), HrProcessNew },
{ IDS_CMD_JOIN, TEXT(""), HrProcessJoin },
{ IDS_CMD_POST, TEXT(""), HrProcessPost },
{ IDS_CMD_INTRO, TEXT(""), HrProcessIntro },
{ IDS_CMD_LEAVE, TEXT(""), HrProcessLeave },
{ IDS_CMD_TOPICS, TEXT(""), HrProcessTopics },
{ IDS_CMD_MEMBERS, TEXT(""), HrProcessMembers },
};

static TCHAR lpszTopicName[MAXSUBJLEN + 1] = {0}; // Topic of current msg.
static LPTSTR lpszCommand = NULL; // Command of current msg.

//$--HrParseCommand()----------------------------------------------------------
// Parses out the command and the topic strings from the subject of the given
// message. One thing we found that we needed to be careful of was to eliminate
// all un-necessary white space from the topic string.
//
// The globals lpszCommand and lpszTopicName are used to contain the result.
// ----------------------------------------------------------------------------

static HRESULT HrParseCommand(
IN LPMESSAGE lpMessage) // Pointer to the current message object
{
HRESULT hr = NOERROR;
TCHAR *pchSubject = lpSenderProps[IDX_SUBJECT].Value.LPSZ;
TCHAR *pchTopic = lpszTopicName;

DEBUGPUBLIC( "HrParseCommand()");

hr = CHK_HrParseCommand( lpMessage);
if( FAILED( hr))
RETURN( hr);

if( pchSubject == NULL)
{
hr = HR_LOG( E_FAIL);
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Extract the command from the subject string.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Skip past any initial white space
while( *pchSubject && isspace(*pchSubject)) pchSubject++;
lpszCommand = pchSubject;

// Skip past command string
while( *pchSubject && !isspace(*pchSubject)) pchSubject++;
if(*pchSubject) *pchSubject++ = TEXT('\0');

if( lstrlen( lpszCommand) > MAXCMDLEN)
{ // The string is longer than any recognizeable command.
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_INVALID_COMMAND));
hr = HR_LOG( E_FAIL);
goto cleanup;
}

if( lstrlen( pchSubject) > MAXSUBJLEN)
{ // The topic name is much longer than a reasonable limit.
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_TOPIC_TOO_LONG));
hr = HR_LOG( E_FAIL);
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Extract the topic string from the subject string, eliminating all
// un-necessary white space.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

for( ;;)
{
// Skip white space
while( *pchSubject && isspace(*pchSubject))
pchSubject++;

if( !*pchSubject)
{ // finish the topic string
*pchTopic = TEXT('\0');
goto cleanup;
}

// Add single space to topic
if( pchTopic > lpszTopicName)
*pchTopic++ = TEXT(' ');

// Add the token to the topic string.
while( *pchSubject && !isspace( *pchSubject))
*pchTopic++ = *pchSubject++;
}

cleanup:
RETURN( hr);
}

//$--HrProcessNew---------------------------------------------------------------
// "New" command handling procedure
// - creates a topic folder (if one does not exist) for the new topic,
// - saves a modified version of the command message in the topic folder.
//
// The intro message is stored in the MAPI_ASSOCIATED portion of the topic
// folder. We gave it a unique message class so that we can use the
// FindRow() function to locate it later. This is a quick opperation
// since very few items will ever be stored in this location.
//
// We chose this method rather than storing it in the main portion of the topic
// folder to avoid sorting the folder, which could get quite large. Sorting a
// folder is an expensive opperation at the server end and should only be used
// when it is necessary.
// -----------------------------------------------------------------------------
static HRESULT HrProcessNew(
IN LPMESSAGE lpMessage) // Pointer to the current message object
{
HRESULT hr = NOERROR;
LPMAPIFOLDER lpNewFolder = NULL;
LPMESSAGE lpNewMsg = NULL;
ULONG iTFolderArray = NOT_FOUND;
ULONG cStrLen = 0;
ULONG cbFolderEID = 0L;
LPENTRYID lpFolderEID = NULL;
STopic* lpTopic = NULL;

DEBUGPUBLIC( "HrProcessNew()");
hr = CHK_HrProcessAny( lpMessage);
if( FAILED( hr))
RETURN( hr);

if( !*lpszTopicName)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
goto cleanup;
}

// If the topic already exists, generate an error report
iTFolderArray = STFolderArray_Find( lpszTopicName);
if( iTFolderArray != NOT_FOUND)
{ // Send an error report
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_TOPIC_EXISTS));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Create the new topic folder and copy the topic introduction message into the
// associated portion of the folder.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Create a new topic folder.
hr = STFolderArray_HrCreateFolder( lpszTopicName, &lpNewFolder, &lpTopic);
if( FAILED(hr))
goto cleanup;

// Copy the command message into the topic folder
hr = HrDuplicateMessage( lpMessage, lpNewFolder, MAPI_ASSOCIATED,
lpPropsToCopyForKeep, &lpNewMsg);
if( FAILED(hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Initialize the topic message.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Set the recipient list to the topic creator.
hr = MAPICALL( lpNewMsg)->ModifyRecipients( lpNewMsg, 0L, lpSenderAddr);
if( FAILED(hr))
goto cleanup;

// Set the Subject field.
cStrLen = LoadString( hInst, IDS_SUBJ_INTRO, (LPTSTR)&szSubjBuf, MAXSUBJLEN);
_tcscat( szSubjBuf, lpszTopicName);

{ // Encapsulate to make code clear
SInitPropValue SPropValues[] =
{
{ PR_SUBJECT, 0, (ULONG) szSubjBuf},
{ PR_MESSAGE_CLASS, 0, (ULONG) INTRO_MSG_CLASS}
};

hr = MAPICALL( lpNewMsg)->SetProps( lpNewMsg,
2L, (LPSPropValue)&SPropValues, NULL);
if( FAILED( hr))
goto cleanup;
}

// Save the changes to the message.
hr = MAPICALL(lpNewMsg)->SaveChanges( lpNewMsg,
FORCE_SAVE | KEEP_OPEN_READWRITE);
if( FAILED(hr))
goto cleanup;

// Let the topic object know about the open message interface pointer.
STopic_SetIntroMsg( lpTopic, lpNewMsg);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add user to the access control list of the public folder. This will allow
// the user to read the folder with the Capone client.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if( bPublicTopic)
{
// This call will fail when the originator is a one-off. Since this feature
// is not necessary to interact with the list server we ignore the error.

HR_LOG( HrModifyACL(
lpNewFolder, // Folder to modify ACL for.
TEXT("Default"), // Name of user to change.
0L, // Byte count for User's entry id.
NULL, // User's entry id.
FALSE, // Do not remove rights.
rightsReadOnly));

HR_LOG( HrModifyACL(
lpNewFolder,
lpSenderProps[ IDX_SENDER_NAME].Value.LPSZ,
lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.cb,
(LPENTRYID)lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.lpb,
FALSE,
dwACLRights));
}

hr = HrSendConfirmReport( lpMessage, IDS_CMD_NEW, IDS_CNF_NEW, NULL);
if( FAILED(hr))
goto cleanup;

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

cleanup:
if( FAILED(hr) && hr != MAPI_E_NETWORK_ERROR && lpNewFolder)
{ // We failed after the topic folder was created so delete it
// from the MAPI store, the topic array, and the topic cache.
HR_LOG( STFolderArray_HrDeleteFolderSZ( lpszTopicName));
}

RETURN( hr);
}

//$--HrProcessJoin----------------------------------------------------------
// "Join" command handling routine
// -------------------------------------------------------------------------
static HRESULT HrProcessJoin(
IN LPMESSAGE lpMessage) // Pointer to the current message object
{
HRESULT hr = NOERROR;
LPMESSAGE lpIntroMsg = NULL;
ULONG iTFolderArray = NOT_FOUND;
ULONG iSubscriber = NOT_FOUND;
STopic* lpTopic = NULL;

DEBUGPUBLIC( "HrProcessJoin()");
hr = CHK_HrProcessAny( lpMessage);
if( FAILED( hr))
RETURN( hr);

if( !*lpszTopicName)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
goto cleanup;
}

// If the topic does not exist generate an error report.
iTFolderArray = STFolderArray_Find( lpszTopicName);
if( iTFolderArray == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
goto cleanup;
}

// Get the topic object from the cache.
hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
if( FAILED(hr))
goto cleanup;

// Check if the sender is already a subscriber of the topic
hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
if( FAILED(hr))
goto cleanup;
if( iSubscriber != NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_IS_SUBSCRIBER));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add user to the access control list of the public folder. This will allow
// the user to read the folder with the Capone client.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if( bPublicTopic)
{
LPMAPIFOLDER lpJoinFolder = STopic_GetFolder( lpTopic);

// This call will fail when the originator is a one-off. Since this feature
// is not necessary to interact with the list server we ignore the error.

HR_LOG( HrModifyACL(
lpJoinFolder,
lpSenderProps[ IDX_SENDER_NAME].Value.LPSZ,
lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.cb,
(LPENTRYID)lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.lpb,
FALSE,
dwACLRights));
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add this JOINer to the recipient list of the topic intro message, and send
// a confirmation to the originator.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Add the subscriber to intro msg and memory buffer.
hr = STopic_HrAddSubscriber( lpTopic, lpSenderAddr);
if( FAILED( hr))
goto cleanup;

// Send a confirmation report to the message originator.
lpIntroMsg = STopic_GetIntroMsg( lpTopic);
hr = HrSendConfirmReport( lpMessage, IDS_CMD_JOIN, IDS_CNF_JOIN, lpIntroMsg);
if( FAILED(hr))
goto cleanup;

cleanup:

RETURN( hr);
}

//$--HrProcessPost----------------------------------------------------------
// "Post" command handling procedure
// - saves a copy of the command message in the topic folder, and
// - distributes the command message to all subscribers of the topic
// -------------------------------------------------------------------------
static HRESULT HrProcessPost(
IN LPMESSAGE lpMessage) // Pointer to the current message object
{
HRESULT hr = NOERROR;
unsigned cStrLen = 0;
ULONG fModRecip = 0L; //ModifyRecipients flag -> replace list
LPMAPIFOLDER lpTopicFolder = NULL;
LPMESSAGE lpPostMsg = NULL;
LPMESSAGE lpSendMsg = NULL;
LPADRLIST lpAdrList = NULL;
ULONG iTFolderArray = NOT_FOUND;
ULONG iSubscriber = NOT_FOUND;
STopic* lpTopic = NULL;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Two copies of the original posted message is made. One to keep in the topic
// folder, and the other to be distributed to the subscribers.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

DEBUGPUBLIC( "HrProcessPost()");
hr = CHK_HrProcessAny( lpMessage);
if( FAILED( hr))
RETURN( hr);

if( !*lpszTopicName)
{
HR_LOG( HrSendErrReport(lpMessage, IDS_ERR_NULL_PARAM));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the topic folder and get the topic and subscriber list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// If the topic does not exist generate an error report.
iTFolderArray = STFolderArray_Find( lpszTopicName);
if( iTFolderArray == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
goto cleanup;
}

// Get the topic object from the cache.
hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
if( FAILED(hr))
goto cleanup;

// Verify that the sender is a subscriber of the topic
hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
if( FAILED(hr))
goto cleanup;
if( iSubscriber == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONSUBSCRIBER));
goto cleanup;
}

// Get the pointer to the topic folder.
lpTopicFolder = STopic_GetFolder( lpTopic);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Save a copy of the posting in the topic folder.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Make a copy of the command message in the topic-folder
hr = HrDuplicateMessage( lpMessage, lpTopicFolder, 0, lpPropsToCopyForKeep, &lpPostMsg);
if( FAILED(hr))
goto cleanup;

// Replace the Subject field with a fixed string
cStrLen = LoadString( hInst, IDS_SUBJ_POST, (LPTSTR)&szSubjBuf, MAXSUBJLEN);
_tcsncat( szSubjBuf, lpszTopicName, MAXSUBJLEN - cStrLen);

hr = HrMAPISetPropString( (LPMAPIPROP)lpPostMsg, PR_SUBJECT, szSubjBuf);
if( FAILED( hr))
goto cleanup;

// Save the changes to the message
hr = MAPICALL(lpPostMsg)->SaveChanges( lpPostMsg, FORCE_SAVE);
if( FAILED( hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Can't send a message that's was created in a public folder, so make a copy of
// the posting in the NDR folder. Place the entire subscriber list in the
// 'To' field.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Make a copy of posted message in NDR folder for resubmission to all
hr = HrDuplicateMessage( lpMessage, lpNDRFolder, 0, lpPropsToCopyForSend, &lpSendMsg);
if( FAILED( hr))
goto cleanup;

// Get a copy of the entire address list of subscribers. We can't use the
// original row set since ModifyRecipients will change it which would be bad.
hr = STopic_GetCopyOfSubscriberRowSet( lpTopic, &lpAdrList);
if( FAILED( hr))
goto cleanup;

// Replace/Add-to the TO:list in the msg. with the subscriber list
hr = MAPICALL( lpSendMsg)->ModifyRecipients(lpSendMsg,
fModRecip, lpAdrList);
if( FAILED( hr))
goto cleanup;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Set additional properties then send the message.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Concatenate the sender's display name onto the subject line.
// Must truncate unusually long topic names so that there is enough space.
{ // Encapusulate to make code clear
TCHAR szSenderName[MAXSUBJLEN + 1] = {0};
ULONG cNameLen = 0L;

cNameLen = LoadString( hInst, IDS_SUBJ_FROM, (LPTSTR)&szSenderName, MAXSUBJLEN);
_tcsncat( szSenderName, lpSenderProps[IDX_SENDER_NAME].Value.LPSZ, MAXSUBJLEN - cNameLen);

cNameLen = _tcslen( szSenderName);
cStrLen = _tcslen( szSubjBuf);

if( cStrLen + cNameLen >= MAXSUBJLEN)
cStrLen = max(MAXSUBJLEN - cNameLen, 15);

_tcsncpy( &szSubjBuf[cStrLen], szSenderName, MAXSUBJLEN - cStrLen);
}

{ // Encapsulate to make code clear
SInitPropValue SPropValues[] =
{
{ PR_SUBJECT, 0, (ULONG) szSubjBuf},
{ PR_DELETE_AFTER_SUBMIT, 0, (ULONG) TRUE},
{ PR_MESSAGE_CLASS, 0, (ULONG) SMBAGENT_MSG_CLASS}
};

hr = MAPICALL( lpSendMsg)->SetProps( lpSendMsg,
3L, (LPSPropValue)&SPropValues, NULL);
if( FAILED( hr))
goto cleanup;
}

hr = MAPICALL(lpSendMsg)->SaveChanges( lpSendMsg, KEEP_OPEN_READWRITE);
if( FAILED( hr))
goto cleanup;

// Distribute the message to all subscribers
hr = MAPICALL(lpSendMsg)->SubmitMessage( lpSendMsg, FORCE_SUBMIT);
if( FAILED( hr))
goto cleanup;

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

cleanup:
FREEPADRLIST( lpAdrList);
ULRELEASE( lpPostMsg);
ULRELEASE( lpSendMsg);

RETURN( hr);
}

//$--HrProcessIntro----------------------------------------------------------
// "Intro" command handling routine
// - Sends a copy of the topic Intro message to the command originator
// -------------------------------------------------------------------------
static HRESULT HrProcessIntro(
IN LPMESSAGE lpMessage) // Pointer to the current message object
{
HRESULT hr = NOERROR;
LPMESSAGE lpIntroMsg = NULL;
LPSRowSet lpRows = NULL;
LPMAPITABLE lpRecipTable = NULL;
ULONG iTFolderArray = NOT_FOUND;
ULONG iSubscriber = NOT_FOUND;
STopic* lpTopic = NULL;
ENTRYLIST elIntroMsg = { 1L, NULL };
SPropTagArray aptEntryID = { 1L, {PR_ENTRYID} };


DEBUGPUBLIC( "HrProcessIntro()");
hr = CHK_HrProcessAny( lpMessage);
if( FAILED( hr))
RETURN( hr);

if( !*lpszTopicName)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the topic folder and get the topic and subscriber list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// If the topic does not exist generate an error report.
iTFolderArray = STFolderArray_Find( lpszTopicName);
if( iTFolderArray == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
goto cleanup;
}

// Get the topic object from the cache.
hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
if( FAILED(hr))
goto cleanup;

// Verify that the sender is a subscriber of the topic
hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
if( FAILED(hr))
goto cleanup;
if( iSubscriber == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONSUBSCRIBER));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Send an confirmation report including the Intro message.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

lpIntroMsg = STopic_GetIntroMsg( lpTopic);
hr = HrSendConfirmReport( lpMessage, IDS_CMD_INTRO, IDS_CNF_INTRO, lpIntroMsg);
if( FAILED(hr))
goto cleanup;

cleanup:
RETURN( hr);
}


//$--HrProcessLeave----------------------------------------------------------
// "Leave" command handling routine
// - deletes the sender's address from the topic's subscriber list.
// -------------------------------------------------------------------------
static HRESULT HrProcessLeave(
IN LPMESSAGE lpMessage) // Pointer to the current message object
{
HRESULT hr = NOERROR;
ULONG iTFolderArray = NOT_FOUND;
ULONG iSubscriber = NOT_FOUND;
ULONG cSubscribers = 0;
STopic* lpTopic = NULL;

DEBUGPUBLIC( "HrProcessLeave()");
hr = CHK_HrProcessAny( lpMessage);
if( FAILED( hr))
RETURN( hr);

if( !*lpszTopicName)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the topic folder and get the topic and subscriber list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// If the topic does not exist generate an error report.
iTFolderArray = STFolderArray_Find( lpszTopicName);
if( iTFolderArray == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
goto cleanup;
}

// Get the topic object from the cache.
hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
if( FAILED(hr))
goto cleanup;

// Verify that the sender is a subscriber of the topic
hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
if( FAILED(hr))
goto cleanup;
if( iSubscriber == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONSUBSCRIBER));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Remove user to the access control list of the public folder.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

if( bPublicTopic)
{
LPMAPIFOLDER lpLeaveFolder = STopic_GetFolder( lpTopic);

// This call will fail when the originator is a one-off. Since this feature
// is not necessary to interact with the list server we ignore the error.

HR_LOG( HrModifyACL(
lpLeaveFolder,
lpSenderProps[ IDX_SENDER_NAME].Value.LPSZ,
lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.cb,
(LPENTRYID)lpSenderProps[ IDX_SENDER_ENTRYID].Value.bin.lpb,
TRUE, // Remove the ACL
dwACLRights));
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Delete the subscriber from the topic or delete the topic if this subscriber
// is the only one left.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Get the count of subscribers of the topic.
cSubscribers = STopic_GetSubscriberCount( lpTopic);

// Is this the last subscriber?
if( cSubscribers == 1)
{ // YES, so delete the topic and all its messages.
hr = STFolderArray_HrDeleteFolder( iTFolderArray);
if( FAILED(hr))
goto cleanup;
}
else
{ // Delete sender from the topic's subscriber list. After this call
// the subscriber portion of the topic will be uninitialized.
hr = STopic_HrDeleteSubscriber( lpTopic, iSubscriber);
if( FAILED(hr))
goto cleanup;
}

// Send a confirmation report
hr = HrSendConfirmReport( lpMessage, IDS_CMD_LEAVE, IDS_CNF_LEAVE, NULL);
if( FAILED( hr))
goto cleanup;

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

cleanup:
RETURN( hr);
}

//$--HrProcessTopics----------------------------------------------------------
// "Topics" command handling routine
// - Sends a list of topics sender is a member of to the command originator
// ----------------------------------------------------------------------------

// Max length of each topic line. +10 to append other things such as ***\t and \n.
#define MAXTOPICSLEN (MAXSUBJLEN + 10)

static HRESULT HrProcessTopics(
IN LPMESSAGE lpMessage) // Pointer to the current message object
{
HRESULT hr = NOERROR;
ULONG cTopics = 0;
ULONG cBytes = 0;
ULONG iTFolderArray = NOT_FOUND;
ULONG iSubscriber = NOT_FOUND;
LPTSTR lpTopicsList = NULL;
LPTSTR lpTopicsEntry = NULL;
BOOL bFolderErr = FALSE;
STopic* lpTopic = NULL;

SizedSPropTagArray(1L,aptTableProps)= { 1L, { PR_DISPLAY_NAME } };

DEBUGPUBLIC( "HrProcessTopics()");
hr = CHK_HrProcessAny( lpMessage);
if( FAILED( hr))
RETURN( hr);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Allocate message buffer large enough for all topic names lines. Each line
// may have three asterisks, a tab, the topic name and a return. Also allow
// for two extra lines worth of space for extra message at end of buffer.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cTopics = STFolderArray_GetCount();
cBytes = (((cTopics + 2) * MAXTOPICSLEN) + 1) * sizeof (TCHAR);
hr = MAPIAllocateBuffer( cBytes, &lpTopicsList);
if( FAILED( hr))
{
hr = HR_LOG( E_OUTOFMEMORY);
goto cleanup;
}
ASSERTERROR( lpTopicsList != NULL, "NULL lpTopicsList!");
memset (lpTopicsList, 0, cBytes);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// List every folder placing three asterisks in front of the ones that the
// sender has subscribed to.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

lpTopicsEntry = lpTopicsList;
for( iTFolderArray = 0; iTFolderArray < cTopics; iTFolderArray ++)
{
LPTSTR lpszSub; // To hold either a blank or asterisks.

// Get the topic object from the cache.
hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
if( SUCCEEDED(hr))
{
// Check to see if the sender is a subscriber of the topic.
hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
if( FAILED(hr))

goto cleanup; 
if( iSubscriber == NOT_FOUND)
lpszSub = TEXT("\t");
else
lpszSub = TEXT("***\t");
}
else
{
lpszSub = TEXT("+++\t");
bFolderErr = TRUE;
}

// Record the topic name in the buffer.
lpTopicsEntry += wsprintf( lpTopicsEntry, TEXT("%s%s\r\n"), lpszSub,
STFolderArray_GetName( iTFolderArray));

ASSERTERROR( lpTopicsEntry <= ((LPBYTE) lpTopicsList) + cBytes, "ERROR: Buffer overwrite!");
}

if( lpTopicsEntry == lpTopicsList)
{ // No topics were listed.
lpTopicsEntry += wsprintf( lpTopicsEntry, TEXT("No topics found.\r\n"));
}
else
{ // At least one topic was listed.
lpTopicsEntry += wsprintf( lpTopicsEntry, TEXT("\r\n***\tindicates you are a subscriber.\r\n"));

if( bFolderErr)
lpTopicsEntry += wsprintf( lpTopicsEntry, TEXT( "+++\tindicates subscription information not found.\r\n"));
}

ASSERTERROR( lpTopicsEntry <= ((LPBYTE) lpTopicsList) + cBytes, "ERROR: Buffer overwrite!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add the list to the original message body, save it, and send it as a
// confirmation report.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

hr = HrMAPISetPropString( (LPMAPIPROP)lpMessage, PR_BODY, lpTopicsList);
if( FAILED(hr))
goto cleanup;

hr = MAPICALL(lpMessage)->SaveChanges( lpMessage, KEEP_OPEN_READWRITE);
if( FAILED(hr))
goto cleanup;

// Send an confirmation report, including the Topics list
hr = HrSendConfirmReport( lpMessage, IDS_CMD_TOPICS, IDS_CNF_TOPICS, NULL);
if( FAILED(hr))
goto cleanup;

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

cleanup:
MAPIFREEBUFFER( lpTopicsList);
RETURN( hr);
}

//$--HrProcessMembers----------------------------------------------------------
// "Members" command handling routine
// - Sends a list of members for a given topic to the command originator
// ----------------------------------------------------------------------------

#define MAXMEMBERLEN 110 // max length of each member's name/address

static HRESULT HrProcessMembers(
IN LPMESSAGE lpMessage) // Pointer to the current message object
{
HRESULT hr = NOERROR;
ULONG cSubscribers = 0;
ULONG cBytes = 0;
LPTSTR lpMemberList = NULL;
LPTSTR lpMemberEntry = NULL;
LPSRow lpRow = NULL;
ULONG iTFolderArray = NOT_FOUND;
ULONG iSubscriber = NOT_FOUND;
STopic* lpTopic = NULL;

DEBUGPUBLIC( "HrProcessMembers()");
hr = CHK_HrProcessAny( lpMessage);
if( FAILED( hr))
RETURN( hr);

if( !*lpszTopicName)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_PARAM));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Find the topic folder and get the topic and subscriber list.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// If the topic does not exist generate an error report.
iTFolderArray = STFolderArray_Find( lpszTopicName);
if( iTFolderArray == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONEXIST_TOPIC));
goto cleanup;
}

// Get the topic object from the cache.
hr = STopicCache_HrGetTopic( iTFolderArray, &lpTopic);
if( FAILED( hr))
goto cleanup;

// Verify that the sender is a subscriber of the topic
hr = STopic_HrFindSubscriber( lpTopic, &iSubscriber);
if( FAILED(hr))
goto cleanup;
if( iSubscriber == NOT_FOUND)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NONSUBSCRIBER));
goto cleanup;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Allocate message buffer for subscriber names each terminated by <return>
// and the total list terminated by zero. Assume each name is MAXNAMELEN
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

cSubscribers = STopic_GetSubscriberCount( lpTopic);
cBytes = ((cSubscribers * MAXMEMBERLEN) + 1) * sizeof(TCHAR);
hr = MAPIAllocateBuffer( cBytes, &lpMemberList);
if( FAILED( hr))
{
hr = HR_LOG( E_OUTOFMEMORY);
goto cleanup;
}
ASSERTERROR( lpMemberList != NULL, "NULL lpMemberList!");
memset (lpMemberList, 0, cBytes);

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Make a list of all of the members with their display name and email address.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

lpMemberEntry = lpMemberList;
lpRow = STopic_GetFirstSubscriber( lpTopic);
while( lpRow) // Loop through all subscribers to the topic
{
// Format display name and address for one subscriber.
lpMemberEntry += wsprintf( lpMemberEntry, TEXT("%.32s (%.64s)\r\n"),
lpRow->GetDispName(), lpRow->GetEmailAddr());

lpRow = STopic_GetNextSubscriber( lpTopic);
}

ASSERTERROR( lpMemberEntry <= ((LPBYTE) lpMemberList) + cBytes, "ERROR: Buffer overwrite!");

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Add the list to the original message body, save it, and send it back in a
// confirmation report.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

hr = HrMAPISetPropString( (LPMAPIPROP)lpMessage, PR_BODY, lpMemberList);
if( FAILED(hr))
goto cleanup;

hr = MAPICALL(lpMessage)->SaveChanges( lpMessage, KEEP_OPEN_READWRITE);
if( FAILED(hr))
goto cleanup;

// Send an confirmation report, including the member list
hr = HrSendConfirmReport( lpMessage, IDS_CMD_MEMBERS, IDS_CNF_MEMBERS, NULL);
if( FAILED(hr))
goto cleanup;

cleanup:
RETURN( hr);
}

//$--HrProcessCommand ------------------------------------------------------
// This procedure is called for each message read from Inbox to:
// - validate the command, and
// - invoke the appropriate handling procedure to process the command.
// -------------------------------------------------------------------------
HRESULT HrProcessCommand(
IN LPMESSAGE lpMessage, // Pointer to the current message object
IN LPENTRYLIST lpelMsgID, // Pointer to ENTRYLIST of the current message
IN BOOL * pfDeleted) // Flag indicating if msg.is deleted upon return
{
HRESULT hr = NOERROR;
int i = 0;
ULONG iErrIndex = 0;
LPTSTR lpszClass = NULL; // Class string of the current msg.
static BOOL fInitialized = FALSE;

DEBUGPUBLIC( "HrProcessCommand()");

hr = CHK_HrProcessCommand( lpMessage, lpelMsgID, pfDeleted);
if( FAILED( hr))
RETURN( hr);

*pfDeleted = FALSE; // initialize

hr = HrGetSenderProps( lpMessage);
if( FAILED(hr))
goto cleanup; // If we can't do this then we can't send an error report.

// Initialize command name strings first time only.
if( !fInitialized)
{
fInitialized = TRUE;
for (i=0; i < (sizeof (aCmdTable) / sizeof (aCmdTable[0])); i++)
{
if (!LoadString( hInst, aCmdTable[i].ulCmdRID, aCmdTable[i].szCmdName, MAXCMDLEN))
{ // ERROR: Command resource string not found!
// We can not continue so we signal the service to stop and
// complete this operation. The actual stopping of the service
// will occur a little later.
EventLogMsg( EDKEVENT_ERROR, 1, "Could not initialize the command table.", 0);
ServiceStop();

// Since we haven't determined the validity of the message, set
// the deleted flag to keep the message from being deleted later.
*pfDeleted = TRUE;

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

// Initialize new topic folder info if config has changed...
hr = HrInitTopicFolders();
if( FAILED( hr))
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_INIT_FAILURE));
goto cleanup;
}

lpszClass = (LPTSTR) lpSenderProps[ IDX_MESSAGE_CLASS].Value.LPSZ;
ASSERT_STRING_PTR(lpszClass, "lpszClass is not a valid string!");

if( _tcsicmp(lpszClass, TEXT("IPM.NOTE")) != 0)
{ // Not an IPM.Note so place in the NDR folder.
hr = MAPICALL(lpInFolder)->CopyMessages( lpInFolder,
lpelMsgID, NULL, lpNDRFolder, 0L, NULL, 0L);
goto cleanup;
}

// Parse for the command in the Subject field
hr = HrParseCommand( lpMessage);
if( FAILED(hr))
goto cleanup;
if( !*lpszCommand)
{
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_NULL_COMMAND));
goto cleanup;
}

// Validate the command by comparing the command token with
// the command entries in the command table; if match found,
// invoke the corresponding handling procedure to process the command
for (i=0; i < (sizeof (aCmdTable) / sizeof (aCmdTable[0])); i++)
{
if( _tcsicmp(lpszCommand, aCmdTable[i].szCmdName) == 0)
{
// NOTE: These command should NOT return an HRESULT error if it sent a mail
// message to the originator due to an error in lpMessage. E_FAILs
// are reserved to indicate an unexpected error in MAPI or a logic error.
hr = (aCmdTable[i].lpfnCmdProc)( lpMessage);
if( FAILED( hr))
{
switch(hr)
{
case MAPI_E_NETWORK_ERROR:
// don't need to log an error
break;

case EDK_E_NOT_FOUND:
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_BAD_TOPIC));
break;

default:
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_SMBAGENT));
break;
}
}

// We are done so we can clean up now.
goto cleanup;
}
}

// Command not in CmdTable; send an error report to originator
HR_LOG( HrSendErrReport( lpMessage, IDS_ERR_INVALID_COMMAND));

cleanup:
MAPIFREEBUFFER( lpSenderProps);
lpszCommand = NULL;

RETURN( hr);
}

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