You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
499 lines
17 KiB
499 lines
17 KiB
#define INITGUID
|
|
#define DEFINE_STRCONST
|
|
|
|
#include <windows.h>
|
|
#include <ole2.h>
|
|
|
|
#include <initguid.h>
|
|
#include <mimeole.h>
|
|
|
|
#include "encode.h"
|
|
|
|
#define CORg(command) \
|
|
if (FAILED(command)) { \
|
|
goto Error; \
|
|
}
|
|
|
|
#define CPRg(command) \
|
|
if (! (command)) { \
|
|
goto Error; \
|
|
}
|
|
|
|
|
|
static LPWSTR s_rgwszValues[] = { NULL };
|
|
|
|
#define DEBUGFILE 1
|
|
#define MAX_LAYERS 3
|
|
|
|
//------------------------------------------------------------------
|
|
//------------------------------------------------------------------
|
|
HRESULT WriteBSTRToMultibyteToStream( const BSTR bstrStr, IStream** ppStream )
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LARGE_INTEGER liZero = {0}; // for ->Seek()
|
|
|
|
char* pszMessage = NULL;
|
|
int len = 0;
|
|
|
|
if (!ppStream) return E_INVALIDARG;
|
|
|
|
len = wcslen(bstrStr);
|
|
CPRg(pszMessage = new char[len + 1]);
|
|
WideCharToMultiByte(CP_ACP, 0, bstrStr, len,
|
|
pszMessage, len + 1,
|
|
NULL, NULL );
|
|
pszMessage[len] = '\0';
|
|
|
|
CORg(CreateStreamOnHGlobal(NULL, TRUE, ppStream));
|
|
CORg((*ppStream)->Seek(liZero, STREAM_SEEK_SET, NULL));
|
|
CORg((*ppStream)->Write(pszMessage, len, NULL));
|
|
CORg((*ppStream)->Seek(liZero, STREAM_SEEK_SET, NULL));
|
|
Error:
|
|
if (pszMessage) delete[] pszMessage;
|
|
return hr;
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
SMimeEncode::SMimeEncode() :
|
|
m_dwFlags(0),
|
|
m_stmOutput(NULL),
|
|
m_szSignAlg(NULL),
|
|
m_szEncryptAlg(NULL),
|
|
m_szBody(NULL),
|
|
m_SigningCertInner(NULL),
|
|
m_SigningCertOuter(NULL),
|
|
m_EncryptionCert(NULL),
|
|
m_hCryptProv(NULL),
|
|
m_hMYCertStore(NULL),
|
|
m_hCACertStore(NULL),
|
|
m_hABCertStore(NULL),
|
|
m_szSenderEmail(NULL),
|
|
m_szSenderName(NULL),
|
|
m_szRecipientEmail(NULL),
|
|
m_szRecipientName(NULL),
|
|
m_szOutputFile(NULL)
|
|
{
|
|
|
|
}
|
|
|
|
#define APPEND_SEPERATOR(subject) \
|
|
if (lstrlen(subject)) { \
|
|
lstrcat(subject, " | "); \
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------
|
|
SMimeEncode::~SMimeEncode()
|
|
{
|
|
// BUGBUG: Should clean up any allocated members
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
HRESULT SMimeEncode::HrConfig(
|
|
DWORD dwFlags,
|
|
LPTSTR lpszBody,
|
|
HCRYPTPROV hCryptProv,
|
|
HCERTSTORE hMYCertStore,
|
|
HCERTSTORE hCACertStore,
|
|
HCERTSTORE hABCertStore,
|
|
PCCERT_CONTEXT lpSigningCertInner,
|
|
PCCERT_CONTEXT lpSigningCertOuter,
|
|
PCCERT_CONTEXT lpEncryptionCert,
|
|
LPTSTR lpszSenderEmail,
|
|
LPTSTR lpszSenderName,
|
|
LPTSTR lpszRecipientEmail,
|
|
LPTSTR lpszRecipientName,
|
|
LPTSTR lpszOutputFile
|
|
)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
static char szSubject[257] = "";
|
|
|
|
if (dwFlags & encode_Encrypt) {
|
|
// specify an encryption algorithm
|
|
// BUGBUG: Hardcoded in Encode
|
|
}
|
|
|
|
if (dwFlags & encode_InnerSign) {
|
|
// specify a signing algorithm
|
|
// BUGBUG: Hardcoded in Encode
|
|
}
|
|
|
|
|
|
if (dwFlags & encode_OuterSign) {
|
|
// specify a signing algorithm
|
|
// BUGBUG: Hardcoded in Encode
|
|
}
|
|
|
|
m_dwFlags = dwFlags;
|
|
m_szBody = lpszBody;
|
|
m_hCryptProv = hCryptProv;
|
|
m_hMYCertStore = hMYCertStore;
|
|
m_hCACertStore = hCACertStore;
|
|
m_hABCertStore = hABCertStore;
|
|
m_SigningCertInner = (PCERT_CONTEXT)lpSigningCertInner;
|
|
m_SigningCertOuter = (PCERT_CONTEXT)lpSigningCertOuter;
|
|
m_EncryptionCert = (PCERT_CONTEXT)lpEncryptionCert;
|
|
m_szSenderEmail = lpszSenderEmail;
|
|
m_szRecipientEmail = lpszRecipientEmail;
|
|
m_szOutputFile = lpszOutputFile;
|
|
|
|
// Set a meaningful subject
|
|
lstrcpy(szSubject, "");
|
|
if (dwFlags & encode_InnerSign) {
|
|
APPEND_SEPERATOR(szSubject);
|
|
if (dwFlags & encode_InnerClear) {
|
|
lstrcat(szSubject, "Clear Sign");
|
|
} else {
|
|
lstrcat(szSubject, "Opaque Sign");
|
|
}
|
|
}
|
|
if (dwFlags & encode_Encrypt) {
|
|
APPEND_SEPERATOR(szSubject);
|
|
lstrcat(szSubject, "Encrypt");
|
|
}
|
|
if (dwFlags & encode_OuterSign) {
|
|
APPEND_SEPERATOR(szSubject);
|
|
if (dwFlags & encode_OuterClear) {
|
|
lstrcat(szSubject, "Clear Sign");
|
|
} else {
|
|
lstrcat(szSubject, "Opaque Sign");
|
|
}
|
|
}
|
|
m_szSubject = szSubject;
|
|
|
|
return(hr);
|
|
}
|
|
|
|
//------------------------------------------------------------------
|
|
HRESULT SMimeEncode::HrExecute(void) {
|
|
// Using the SMIME engine:
|
|
//
|
|
// Build the message tree (attach the body)
|
|
// CoCreateInstance( CLSID_IMimeSecurity )
|
|
// InitNew()
|
|
// pSMIMEEngine->EncodeBody( IMimeMessageTree*,
|
|
// hRoot,
|
|
// SEF_??? | EBF_RECURSE ) OR ???
|
|
// HrEncodeOpaque( psi, pTree, hbody, pencoderoot, pstmOut ) ????
|
|
//
|
|
HRESULT hr = S_OK;
|
|
LARGE_INTEGER liZero = {0}; // for ->Seek()
|
|
IStream* pBuildStream = NULL; // scratch stream
|
|
IStream* pResultStream = NULL; // scratch stream
|
|
IMimeMessage* pMimeRoot = NULL; // message in process
|
|
IMimeBody* pMimeRootBody = NULL; // another version
|
|
IMimeInternational* pCharSet = NULL;
|
|
HCHARSET HCharset = 0;
|
|
SYSTEMTIME stNow;
|
|
PROPVARIANT var;
|
|
HBODY hbBody;
|
|
IMimeSecurity* pMimeSecurity = NULL;
|
|
ULONG dwSecurityType = MST_NONE;
|
|
IPersistFile* pIPFFileStore = NULL;
|
|
HRESULT hrLocal = S_OK;
|
|
WCHAR szwFileName[MAX_PATH + 1];
|
|
CHAR szFrom[2 * (MAX_PATH + 1) + 1];
|
|
|
|
// Multilayer stuff
|
|
BOOL fTripleWrap = m_dwFlags & encode_OuterSign;
|
|
ULONG ulSecurityLayers = 0;
|
|
ULONG iEncryptLayer = (ULONG)-1;
|
|
ULONG iInnerSignLayer = (ULONG)-1;
|
|
ULONG iOuterSignLayer = (ULONG)-1;
|
|
// Arrays of option values to set
|
|
DWORD rgdwSecurityType[MAX_LAYERS] = {0};
|
|
PCCERT_CONTEXT rgdwCertSigning[MAX_LAYERS] = {0};
|
|
HCERTSTORE rgdwhCertStore[MAX_LAYERS] = {0}; // optional
|
|
DWORD rgdwUserValidity[MAX_LAYERS] = {0}; // decode only
|
|
DWORD rgdwROMsgValidity[MAX_LAYERS] = {0}; // decode only
|
|
FILETIME rgftSigntime[MAX_LAYERS] = {0}; // optional
|
|
PROPVARIANT rgpvAlgHash[MAX_LAYERS] = {0};
|
|
PROPVARIANT rgpvSymcaps[MAX_LAYERS] = {0};
|
|
PROPVARIANT rgpvAuthattr[MAX_LAYERS] = {0}; // optional
|
|
PROPVARIANT rgpvUnauthattr[MAX_LAYERS] = {0}; // optional
|
|
|
|
|
|
// This is the ALOGORITHM ID for SHA1, default supported signing alg
|
|
const BYTE c_SHA1_ALGORITHM_ID[] =
|
|
{0x30, 0x09, 0x30, 0x07, 0x06, 0x05, 0x2B, 0x0E,
|
|
0x03, 0x02, 0x1A};
|
|
|
|
// This is the ALOGORITHM ID for RC2 -- 40 bit, the default encrypt
|
|
const BYTE c_RC2_40_ALGORITHM_ID[] =
|
|
{0x30, 0x0F, 0x30, 0x0D, 0x06, 0x08, 0x2A, 0x86,
|
|
0x48, 0x86, 0xF7, 0x0D, 0x03, 0x02, 0x02, 0x01,
|
|
0x28};
|
|
|
|
|
|
// get the signing cert from my store
|
|
if (! m_hCryptProv || ! m_hMYCertStore || ! m_hCACertStore || ! m_hABCertStore) {
|
|
hr = E_FAIL;
|
|
goto Error;
|
|
}
|
|
|
|
|
|
// Create the Message object
|
|
//
|
|
CORg(CoCreateInstance(CLSID_IMimeMessage, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_IMimeMessage, (LPVOID*)&pMimeRoot));
|
|
|
|
CORg(pMimeRoot->InitNew());
|
|
|
|
|
|
CORg(CreateStreamOnHGlobal( NULL, TRUE, &pBuildStream));
|
|
CORg(pBuildStream->Seek(liZero, STREAM_SEEK_SET, NULL));
|
|
CORg(pBuildStream->Write(m_szBody, lstrlen(m_szBody), NULL));
|
|
CORg(pBuildStream->Seek(liZero, STREAM_SEEK_SET, NULL));
|
|
|
|
CORg(pMimeRoot->SetTextBody(TXT_PLAIN, IET_8BIT, NULL, pBuildStream, &hbBody));
|
|
|
|
// Create the formatted From address
|
|
if (m_szSenderName) {
|
|
lstrcpy(szFrom, "\"");
|
|
lstrcat(szFrom, m_szSenderName);
|
|
lstrcat(szFrom, "\" ");
|
|
} else {
|
|
lstrcpy(szFrom, "");
|
|
}
|
|
lstrcat(szFrom, "<");
|
|
lstrcat(szFrom, m_szSenderEmail);
|
|
lstrcat(szFrom, ">");
|
|
|
|
var.vt = VT_LPSTR;
|
|
var.pszVal = szFrom; // From Email
|
|
|
|
CORg(hr = pMimeRoot->SetProp(PIDTOSTR(PID_HDR_FROM), 0, &var));
|
|
|
|
|
|
var.vt = VT_LPSTR; // ignored?
|
|
var.pszVal = (LPSTR) STR_MIME_TEXT_PLAIN;
|
|
CORg(pMimeRoot->SetBodyProp(hbBody, STR_HDR_CNTTYPE, 0, &var));
|
|
|
|
var.vt = VT_LPSTR; // ignored?
|
|
var.pszVal = (LPSTR) STR_ENC_QP;
|
|
CORg(pMimeRoot->SetBodyProp(hbBody, STR_HDR_CNTXFER, 0, &var));
|
|
|
|
// Set subject
|
|
var.vt = VT_LPSTR;
|
|
var.pszVal = (LPSTR) m_szSubject;
|
|
CORg(pMimeRoot->SetBodyProp(hbBody, STR_HDR_SUBJECT, 0, &var));
|
|
|
|
CORg(pMimeRoot->BindToObject(HBODY_ROOT, IID_IMimeBody, (LPVOID*)&pMimeRootBody));
|
|
|
|
|
|
//
|
|
// Set the security options
|
|
//
|
|
|
|
// How many layers?
|
|
if (m_dwFlags & encode_InnerSign) {
|
|
iInnerSignLayer = ulSecurityLayers;
|
|
ulSecurityLayers++;
|
|
}
|
|
if (m_dwFlags & encode_Encrypt) {
|
|
iEncryptLayer = ulSecurityLayers; // index in arrays
|
|
ulSecurityLayers++;
|
|
}
|
|
if (m_dwFlags & encode_OuterSign) {
|
|
iOuterSignLayer = ulSecurityLayers;
|
|
ulSecurityLayers++;
|
|
}
|
|
|
|
|
|
// Set up for Inner Signing
|
|
if (m_dwFlags & encode_InnerSign) {
|
|
// specifiy the Security Type for this layer
|
|
rgdwSecurityType[iInnerSignLayer] = m_dwFlags & encode_InnerClear ? MST_THIS_SIGN : MST_THIS_BLOBSIGN;
|
|
dwSecurityType |= m_dwFlags & encode_InnerClear ? MST_THIS_SIGN : MST_THIS_BLOBSIGN;
|
|
|
|
// Specify the Signing Time for this layer
|
|
GetSystemTime(&stNow);
|
|
SystemTimeToFileTime(&stNow, &rgftSigntime[iInnerSignLayer]);
|
|
|
|
// specify the signature alg for this layer
|
|
rgpvAlgHash[iInnerSignLayer].vt = VT_BLOB;
|
|
rgpvAlgHash[iInnerSignLayer].blob.cbSize = sizeof(c_SHA1_ALGORITHM_ID);
|
|
rgpvAlgHash[iInnerSignLayer].blob.pBlobData = (BYTE*)c_SHA1_ALGORITHM_ID;
|
|
|
|
// Specify the signing cert for this layer
|
|
rgdwCertSigning[iInnerSignLayer] = m_SigningCertInner;
|
|
|
|
// HCERTSTORE rgdwhCertStore[MAX_LAYERS] = {0}; // optional
|
|
// PROPVARIANT rgpvSymcaps[MAX_LAYERS] = {0};
|
|
// PROPVARIANT rgpvAuthattr[MAX_LAYERS] = {0}; // optional
|
|
// PROPVARIANT rgpvUnauthattr[MAX_LAYERS] = {0}; // optional
|
|
}
|
|
|
|
// Set up for Outer Signing
|
|
if (m_dwFlags & encode_OuterSign) {
|
|
// specifiy the Security Type for this layer
|
|
rgdwSecurityType[iOuterSignLayer] = m_dwFlags & encode_InnerClear ? MST_THIS_SIGN : MST_THIS_BLOBSIGN;
|
|
dwSecurityType |= m_dwFlags & encode_OuterClear ? MST_THIS_SIGN : MST_THIS_BLOBSIGN;
|
|
|
|
// Specify the Signing Time for this layer
|
|
GetSystemTime(&stNow);
|
|
SystemTimeToFileTime(&stNow, &rgftSigntime[iOuterSignLayer]);
|
|
|
|
// specify the signature alg for this layer
|
|
rgpvAlgHash[iOuterSignLayer].vt = VT_BLOB;
|
|
rgpvAlgHash[iOuterSignLayer].blob.cbSize = sizeof(c_SHA1_ALGORITHM_ID);
|
|
rgpvAlgHash[iOuterSignLayer].blob.pBlobData = (BYTE*)c_SHA1_ALGORITHM_ID;
|
|
|
|
// Specify the signing cert for this layer
|
|
rgdwCertSigning[iOuterSignLayer] = m_SigningCertOuter;
|
|
|
|
// HCERTSTORE rgdwhCertStore[MAX_LAYERS] = {0}; // optional
|
|
// PROPVARIANT rgpvSymcaps[MAX_LAYERS] = {0};
|
|
// PROPVARIANT rgpvAuthattr[MAX_LAYERS] = {0}; // optional
|
|
// PROPVARIANT rgpvUnauthattr[MAX_LAYERS] = {0}; // optional
|
|
}
|
|
|
|
// Set up for Encrypting
|
|
if (m_dwFlags & encode_Encrypt) {
|
|
HCERTSTORE aCertStores[3];
|
|
|
|
//
|
|
// BUGBUG: Hardcoded to RC2 40-bit
|
|
var.vt = VT_BLOB;
|
|
var.blob.cbSize = sizeof( c_RC2_40_ALGORITHM_ID );
|
|
var.blob.pBlobData = (BYTE*) c_RC2_40_ALGORITHM_ID;
|
|
CORg(hr = pMimeRootBody->SetOption(OID_SECURITY_ALG_BULK, &var));
|
|
|
|
// for encryption, get to the right cert store....
|
|
//
|
|
var.caul.cElems = 3;
|
|
aCertStores[0] = CertDuplicateStore(m_hCACertStore);
|
|
aCertStores[1] = CertDuplicateStore(m_hMYCertStore);
|
|
aCertStores[2] = CertDuplicateStore(m_hABCertStore);
|
|
var.caul.pElems = (ULONG*)aCertStores;
|
|
CORg(hr = pMimeRootBody->SetOption(OID_SECURITY_SEARCHSTORES, &var));
|
|
|
|
var.vt = VT_VECTOR | VT_UI4;
|
|
var.caul.cElems = 1;
|
|
var.caul.pElems = (ULONG*)&m_EncryptionCert;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_RG_CERT_ENCRYPT, &var));
|
|
|
|
#ifdef BUGBUG // This isn't right, is it?
|
|
// include the cert...
|
|
var.vt = VT_VECTOR | VT_UI4;
|
|
var.caul.cElems = 1;
|
|
var.caul.pElems = (ULONG*)&m_EncryptionCert;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_RG_CERT_BAG, &var));
|
|
#endif // OLD_STUFF
|
|
|
|
dwSecurityType |= MST_THIS_ENCRYPT;
|
|
rgdwSecurityType[iEncryptLayer] = MST_THIS_ENCRYPT;
|
|
}
|
|
|
|
// Set the OID_SECURITY_TYPE
|
|
if (fTripleWrap) {
|
|
var.vt = VT_VECTOR | VT_UI4;
|
|
var.caul.cElems = ulSecurityLayers;
|
|
var.caul.pElems = rgdwSecurityType;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_TYPE_RG, &var));
|
|
|
|
var.vt = VT_VECTOR | VT_FILETIME;
|
|
var.cafiletime.cElems = ulSecurityLayers;
|
|
var.cafiletime.pElems = rgftSigntime;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_SIGNTIME_RG, &var));
|
|
|
|
var.vt = VT_VECTOR | VT_UI4;
|
|
var.caul.cElems = ulSecurityLayers;
|
|
var.caul.pElems = (DWORD *)rgdwCertSigning;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_CERT_SIGNING_RG, &var));
|
|
|
|
var.vt = VT_VECTOR | VT_VARIANT;
|
|
var.capropvar.cElems = ulSecurityLayers;
|
|
var.capropvar.pElems = rgpvAlgHash;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_ALG_HASH_RG, &var));
|
|
|
|
var.vt = VT_VECTOR | VT_VARIANT;
|
|
var.capropvar.cElems = ulSecurityLayers;
|
|
var.capropvar.pElems = rgpvAlgHash;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_ALG_HASH_RG, &var));
|
|
|
|
} else {
|
|
// Security Type
|
|
var.vt = VT_UI4;
|
|
var.ulVal = dwSecurityType;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_TYPE, &var));
|
|
|
|
if (dwSecurityType & MST_SIGN_MASK) {
|
|
// Signing Time
|
|
var.vt = VT_FILETIME;
|
|
memcpy(&var.filetime, &rgftSigntime[iInnerSignLayer], sizeof(FILETIME));
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_SIGNTIME, &var));
|
|
|
|
// Hash Algorithm
|
|
var.vt = VT_BLOB;
|
|
memcpy(&var.blob, &rgpvAlgHash[iInnerSignLayer].blob, sizeof(BLOB));
|
|
CORg(hr = pMimeRootBody->SetOption(OID_SECURITY_ALG_HASH, &var));
|
|
|
|
// Signing Cert
|
|
var.vt = VT_UI4;
|
|
var.ulVal = (ULONG)m_SigningCertInner;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_CERT_SIGNING, &var));
|
|
}
|
|
}
|
|
|
|
|
|
// Set the HWND for CAPI calls
|
|
var.vt = VT_UI4;
|
|
var.ulVal = 0;
|
|
CORg(pMimeRootBody->SetOption(OID_SECURITY_HWND_OWNER, &var));
|
|
|
|
|
|
// all built, get rid of the shadow pointer we are holding
|
|
//
|
|
pMimeRootBody->Release();
|
|
pMimeRootBody = NULL;
|
|
|
|
pMimeRoot->Commit(0);
|
|
|
|
// SMIME Engine
|
|
//
|
|
CORg(CoCreateInstance(CLSID_IMimeSecurity, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_IMimeSecurity, (LPVOID*) &pMimeSecurity));
|
|
|
|
CORg(pMimeSecurity->InitNew());
|
|
|
|
// ERRORMESSAGE( Unable to encrypt/encode string )
|
|
CORg(pMimeSecurity->EncodeBody(pMimeRoot, HBODY_ROOT,
|
|
EBF_RECURSE | SEF_SENDERSCERTPROVIDED | SEF_ENCRYPTWITHNOSENDERCERT |
|
|
EBF_COMMITIFDIRTY));
|
|
|
|
// Get an Hcharset to force the encoding correctly
|
|
//
|
|
CORg(CoCreateInstance(CLSID_IMimeInternational, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_IMimeInternational, (LPVOID*)&pCharSet));
|
|
|
|
CORg(pCharSet->FindCharset("UTF-8", &HCharset));
|
|
|
|
CORg(pMimeRoot->SetCharset(HCharset, // HCharset
|
|
CSET_APPLY_ALL)); // Applytype
|
|
|
|
|
|
// dump it to a file
|
|
//
|
|
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, m_szOutputFile, -1, szwFileName, MAX_PATH);
|
|
CORg(pMimeRoot->QueryInterface(IID_IPersistFile, (LPVOID*)&pIPFFileStore));
|
|
CORg(pIPFFileStore->Save(szwFileName, FALSE));
|
|
|
|
|
|
// extract the whole message into a stream
|
|
//
|
|
CORg(CreateStreamOnHGlobal(NULL, TRUE, &pResultStream));
|
|
CORg(pMimeRoot->Save(pResultStream, FALSE));
|
|
|
|
Error:
|
|
if (pBuildStream) pBuildStream->Release();
|
|
if (pResultStream) pResultStream->Release();
|
|
if (pMimeRoot) pMimeRoot->Release();
|
|
if (pMimeRootBody) pMimeRootBody->Release();
|
|
if (pCharSet) pCharSet->Release();
|
|
if (pMimeSecurity) pMimeSecurity->Release();
|
|
if (pIPFFileStore) pIPFFileStore->Release();
|
|
|
|
return(hr);
|
|
}
|