/*++


Copyright (c) 1997  Microsoft Corporation

Module Name:

    crmap.cxx

Abstract:

    ADSIIS cert mapper object

Author:

    Philippe Choquier (phillich)    10-Apr-1997

--*/

#include "iisext.hxx"
#pragma hdrstop

#include <nsepname.hxx>
#include <dbgutil.h>

DEFINE_IPrivateDispatch_Implementation(CIISDsCrMap)
DEFINE_DELEGATING_IDispatch_Implementation(CIISDsCrMap)
DEFINE_CONTAINED_IADs_Implementation(CIISDsCrMap)
DEFINE_IADsExtension_Implementation(CIISDsCrMap)

#define LOCAL_MAX_SIZE 32

//
// Local functions
//

HRESULT
GetStringFromBSTR( 
    BSTR    bstr,
    LPSTR*  psz,
    LPDWORD pdwLen,
    BOOL    fAddDelimInCount = TRUE
    );

HRESULT
GetStringFromVariant( 
    VARIANT*    pVar,
    LPSTR*      psz,
    LPDWORD     pdwLen,
    BOOL        fAddDelimInCount = TRUE
    );

VOID
FreeString( 
    LPSTR   psz 
    );

HRESULT
SetBSTR( 
    BSTR*   pbstrRet,
    DWORD   cch, 
    LPBYTE  sz 
    );

HRESULT
SetVariantAsByteArray(
    VARIANT*    pvarReturn, 
    DWORD       cbLen,
    LPBYTE      pbIn 
    );

HRESULT
SetVariantAsBSTR(
    VARIANT*    pvarReturn, 
    DWORD       cbLen,
    LPBYTE      pbIn 
    );

HRESULT
SetVariantAsLong(
    VARIANT*    pvarReturn, 
    DWORD       dwV
    );

HRESULT 
VariantResolveDispatch(
    VARIANT *   pVarOut, 
    VARIANT *   pVarIn
    );

//
//
//

HRESULT
CIISDsCrMap::CreateMapping(
    VARIANT     vCert,
    BSTR        bstrNtAcct,
    BSTR        bstrNtPwd,
    BSTR        bstrName,
    LONG        lEnabled
    )
/*++

Routine Description:

    Create a mapping entry

Arguments:

    vCert - X.509 certificate
    bstrNtAcct - NT acct to map to
    bstrNtPwd - NT pwd
    bstrName - friendly name for mapping entry
    lEnabled - 1 to enable mapping entry, 0 to disable it

Returns:

    COM status

--*/
{
    HRESULT     hres;
    LPBYTE      pbCert = NULL;
    DWORD       cCert;
    LPSTR       pszNtAcct = NULL;
    LPSTR       pszNtPwd = NULL;
    LPSTR       pszName = NULL;
    LPBYTE      pRes;
    DWORD       cRes;
    DWORD       cName;
    DWORD       cNtAcct;
    DWORD       cNtPwd;
    WCHAR       achIndex[LOCAL_MAX_SIZE];
    VARIANT     vOldAcct;
    VARIANT     vOldCert;
    VARIANT     vOldPwd;
    VARIANT     vOldName;
    VARIANT     vOldEnabledFlag;
    PCCERT_CONTEXT pcCert = NULL;

    //
    // Do some sanity checks on the cert 
    //
    if ( SUCCEEDED( hres = GetStringFromVariant( &vCert, 
                                                 (LPSTR*)&pbCert,
                                                 &cCert,
                                                 FALSE ) ) )
    {
        //
        // try to construct a cert context
        //
        if ( !( pcCert = CertCreateCertificateContext( X509_ASN_ENCODING,
                                                       pbCert,
                                                       cCert ) ) )
        {
            DBGPRINTF((DBG_CONTEXT,
                       "Invalid cert passed to CreateMapping()\n"));
            //
            // If the decoding fails, GetLastError() returns an ASN1 decoding
            // error that is obtained by subtracting CRYPT_E_OSS_ERROR from the returned
            // error and looking in file asn1code.h for the actual error. To avoid the
            // cryptic ASN1 errors, we'll just return a general "invalid arg" error 
            //
            hres = RETURNCODETOHRESULT( E_INVALIDARG );
            FreeString( (LPSTR) pbCert );
            goto Exit;
        }

        CertFreeCertificateContext( pcCert );
    }
    else
    {
        goto Exit;
    }

    //
    // check if we already have a mapping for this cert; if we do, we'll replace that mapping
    // with the new one
    //
    if ( SUCCEEDED( hres = GetMapping( IISMAPPER_LOCATE_BY_CERT,
                                       vCert,
                                       &vOldCert,
                                       &vOldAcct,
                                       &vOldPwd,
                                       &vOldName,
                                       &vOldEnabledFlag ) ) )
    {
        DBGPRINTF((DBG_CONTEXT,
                   "Replacing old 1-1 cert mapping with new mapping\n"));

        if ( FAILED( hres = SetName( IISMAPPER_LOCATE_BY_CERT,
                                     vCert,
                                     bstrName ) ) ||
             FAILED( hres = SetAcct( IISMAPPER_LOCATE_BY_CERT,
                                     vCert,
                                     bstrNtAcct ) ) ||
             FAILED( hres = SetPwd( IISMAPPER_LOCATE_BY_CERT,
                                    vCert,
                                    bstrNtPwd ) ) ||
             FAILED( hres = SetEnabled( IISMAPPER_LOCATE_BY_CERT,
                                        vCert,
                                        lEnabled ) ) )
        {
            hres; //NOP - Something failed 
        }
    }
    //
    // New mapping
    //
    else if ( hres == RETURNCODETOHRESULT( ERROR_PATH_NOT_FOUND ) )
    {
        //
        // check mapping exists, create if not
        //
        hres = OpenMd( L"Cert11", METADATA_PERMISSION_WRITE|METADATA_PERMISSION_READ );

        if ( hres == RETURNCODETOHRESULT( ERROR_PATH_NOT_FOUND ) )
        {
            if ( SUCCEEDED( hres = OpenMd( L"", 
                                           METADATA_PERMISSION_WRITE|METADATA_PERMISSION_READ ) ) )
            {
                hres = CreateMdObject( L"Cert11" );
                CloseMd( FALSE );

                // Reopen to the correct node.
                hres = OpenMd( L"Cert11", METADATA_PERMISSION_WRITE|METADATA_PERMISSION_READ );
            }
        }

        if ( FAILED( hres ) )
        {
            goto Exit;
        }

        //
        // adding mapping cert "0" means add @ end of list
        //

        if ( SUCCEEDED( hres = CreateMdObject( L"mappings/0" ) ) )
        {
            if ( SUCCEEDED( hres = GetMdData( L"", MD_NSEPM_ACCESS_CERT, DWORD_METADATA, &
                                              cRes, &pRes ) ) )
            {
                if ( cRes == sizeof(DWORD ) )
                {
                    wsprintfW( achIndex, L"mappings/%u", *(LPDWORD)pRes );

                    if ( FAILED( hres = GetStringFromBSTR( bstrNtAcct, &pszNtAcct, &cNtAcct ) ) ||
                         FAILED( hres = GetStringFromBSTR( bstrNtPwd, &pszNtPwd, &cNtPwd ) ) ||
                         FAILED( hres = GetStringFromBSTR( bstrName, &pszName, &cName ) ) ||
                         FAILED( hres = SetMdData( achIndex, MD_MAPENABLED, DWORD_METADATA, 
                                                   sizeof(DWORD), (LPBYTE)&lEnabled ) ) ||
                         FAILED( hres = SetMdData( achIndex, MD_MAPNAME, STRING_METADATA, 
                                                   cName, (LPBYTE)pszName ) ) ||
                         FAILED( hres = SetMdData( achIndex, MD_MAPNTPWD, STRING_METADATA, 
                                                   cNtPwd, (LPBYTE)pszNtPwd ) ) ||
                         FAILED( hres = SetMdData( achIndex, MD_MAPNTACCT, STRING_METADATA, 
                                                   cNtAcct, (LPBYTE)pszNtAcct ) ) ||
                         FAILED( hres = SetMdData( achIndex, MD_MAPCERT, BINARY_METADATA, 
                                                   cCert, (LPBYTE)pbCert ) ) )
                    {
                    }
                }   
                else
                {
                    hres = E_FAIL;
                }
            }
        }
    }

    CloseMd( SUCCEEDED( hres ) );

    FreeString( (LPSTR)pbCert );
    FreeString( pszNtAcct );
    FreeString( pszNtPwd );
    FreeString( pszName );

Exit:

    return hres;
}


HRESULT
CIISDsCrMap::GetMapping(
    LONG        lMethod,
    VARIANT     vKey,
    VARIANT*    pvCert,
    VARIANT*    pbstrNtAcct,
    VARIANT*    pbstrNtPwd,
    VARIANT*    pbstrName,
    VARIANT*    plEnabled
    )
/*++

Routine Description:

    Get a mapping entry using key

Arguments:

    lMethod - method to use for access ( IISMAPPER_LOCATE_BY_* )
    vKey - key to use to locate mapping
    pvCert - X.509 certificate
    pbstrNtAcct - NT acct to map to
    pbstrNtPwd - NT pwd
    pbstrName - friendly name for mapping entry
    plEnabled - 1 to enable mapping entry, 0 to disable it

Returns:

    COM status

--*/
{
    WCHAR       achIndex[LOCAL_MAX_SIZE];
    HRESULT     hres;
    DWORD       dwLen;
    LPBYTE      pbData;

    VariantInit( pvCert );
    VariantInit( pbstrNtAcct );
    VariantInit( pbstrNtPwd );
    VariantInit( pbstrName );
    VariantInit( plEnabled );

    if ( SUCCEEDED( hres = OpenMd( L"Cert11", 
                                   METADATA_PERMISSION_WRITE|METADATA_PERMISSION_READ ) ) )
    {
        if ( SUCCEEDED(hres = Locate( lMethod, vKey, achIndex )) )
        {
            if ( SUCCEEDED( hres = GetMdData( achIndex, MD_MAPCERT, BINARY_METADATA, &dwLen, 
                                              &pbData ) ) )
            {
                hres = SetVariantAsByteArray( pvCert, dwLen, pbData );
                LocalFree( pbData );
            }
            else
            {
                goto Done;
            }

            if ( SUCCEEDED( hres = GetMdData( achIndex, MD_MAPNTACCT, STRING_METADATA, &dwLen, 
                                              &pbData ) ) )
            {
                hres = SetVariantAsBSTR( pbstrNtAcct, dwLen, pbData );
                LocalFree( pbData );
            }
            else
            {
                goto Done;
            }

            if ( SUCCEEDED( hres = GetMdData( achIndex, MD_MAPNTPWD, STRING_METADATA, &dwLen, 
                                              &pbData ) ) )
            {
                hres = SetVariantAsBSTR( pbstrNtPwd, dwLen, pbData );
                LocalFree( pbData );
            }
            else
            {
                goto Done;
            }

            if ( SUCCEEDED( hres = GetMdData( achIndex, MD_MAPNAME, STRING_METADATA, &dwLen, 
                                              &pbData ) ) )
            {
                hres = SetVariantAsBSTR( pbstrName, dwLen, pbData );
                LocalFree( pbData );
            }
            else
            {
                goto Done;
            }

            if ( FAILED( hres = GetMdData( achIndex, MD_MAPENABLED, STRING_METADATA, &dwLen, 
                                           &pbData ) ) )
            {
                SetVariantAsLong( plEnabled, FALSE );
            }
            else
            {
                SetVariantAsLong( plEnabled, *(LPDWORD)pbData );
                LocalFree( pbData );
            }
        }

Done:
        CloseMd( FALSE );
    }

    return hres;
}


HRESULT
CIISDsCrMap::DeleteMapping(
    LONG        lMethod,
    VARIANT     vKey
    )
/*++

Routine Description:

    Delete a mapping entry using key

Arguments:

    lMethod - method to use for access ( IISMAPPER_LOCATE_BY_* )
    vKey - key to use to locate mapping

Returns:

    COM status

--*/
{
    WCHAR       achIndex[LOCAL_MAX_SIZE];
    HRESULT     hres;

    if ( SUCCEEDED( hres = OpenMd( L"Cert11", 
                                   METADATA_PERMISSION_WRITE|METADATA_PERMISSION_READ ) ) )
    {
        if ( SUCCEEDED(hres = Locate( lMethod, vKey, achIndex )) )
        {
            hres = DeleteMdObject( achIndex );
        }
        CloseMd( TRUE );
    }

    return hres;
}


HRESULT
CIISDsCrMap::SetEnabled(
    LONG        lMethod,
    VARIANT     vKey,
    LONG        lEnabled
    )
/*++

Routine Description:

    Set the enable flag on a mapping entry using key

Arguments:

    lMethod - method to use for access ( IISMAPPER_LOCATE_BY_* )
    vKey - key to use to locate mapping
    lEnabled - 1 to enable, 0 to disable

Returns:

    COM status

--*/
{
    WCHAR       achIndex[LOCAL_MAX_SIZE];
    HRESULT     hres;

    if ( SUCCEEDED( hres = OpenMd( L"Cert11", 
                                   METADATA_PERMISSION_WRITE|METADATA_PERMISSION_READ ) ) )
    {
        if ( SUCCEEDED(hres = Locate( lMethod, vKey, achIndex )) )
        {
            hres = SetMdData( achIndex, MD_MAPENABLED, DWORD_METADATA, sizeof(DWORD), (LPBYTE)&lEnabled );
        }
        CloseMd( TRUE );
    }

    return hres;
}


HRESULT
CIISDsCrMap::SetName(
    LONG        lMethod,
    VARIANT     vKey,
    BSTR        bstrName
    )
/*++

Routine Description:

    Set the Name on a mapping entry using key

Arguments:

    lMethod - method to use for access ( IISMAPPER_LOCATE_BY_* )
    vKey - key to use to locate mapping
    bstrName - name to assign to mapping entry

Returns:

    COM status

--*/
{
    return SetString( lMethod, vKey, bstrName, MD_MAPNAME );
}


HRESULT
CIISDsCrMap::SetString(
    LONG        lMethod,
    VARIANT     vKey,
    BSTR        bstrName,
    DWORD       dwProp
    )
/*++

Routine Description:

    Set a string property on a mapping entry using key

Arguments:

    lMethod - method to use for access ( IISMAPPER_LOCATE_BY_* )
    vKey - key to use to locate mapping
    bstrName - string content to assign to mapping entry
    dwProp - property ID to assign to

Returns:

    COM status

--*/
{
    WCHAR       achIndex[LOCAL_MAX_SIZE];
    LPSTR       pszName = NULL;
    HRESULT     hres;
    DWORD       dwLen;


    if ( FAILED( hres = GetStringFromBSTR( bstrName, &pszName, &dwLen, TRUE ) ) )
    {
        return hres;
    }

    if ( SUCCEEDED( hres = OpenMd( L"Cert11", 
                                   METADATA_PERMISSION_WRITE|METADATA_PERMISSION_READ ) ) )
    {
        if ( SUCCEEDED(hres = Locate( lMethod, vKey, achIndex )) )
        {
            hres = SetMdData( achIndex, dwProp, STRING_METADATA, dwLen, (LPBYTE)pszName );
        }
        CloseMd( TRUE );
    }

    FreeString( pszName );

    return hres;
}


HRESULT
CIISDsCrMap::SetPwd(
    LONG        lMethod,
    VARIANT     vKey,
    BSTR        bstrPwd
    )
/*++

Routine Description:

    Set the Password on a mapping entry using key

Arguments:

    lMethod - method to use for access ( IISMAPPER_LOCATE_BY_* )
    vKey - key to use to locate mapping
    bstrPwd - password to assign to mapping entry

Returns:

    COM status

--*/
{
    return SetString( lMethod, vKey, bstrPwd, MD_MAPNTPWD );
}


HRESULT
CIISDsCrMap::SetAcct(
    LONG        lMethod,
    VARIANT     vKey,
    BSTR        bstrAcct
    )
/*++

Routine Description:

    Set the NT account name on a mapping entry using key

Arguments:

    lMethod - method to use for access ( IISMAPPER_LOCATE_BY_* )
    vKey - key to use to locate mapping
    bstrAcct - NT account name to assign to mapping entry

Returns:

    COM status

--*/
{
    return SetString( lMethod, vKey, bstrAcct, MD_MAPNTACCT );
}



////


HRESULT
CIISDsCrMap::OpenMd(
    LPWSTR  pszOpenPath,
    DWORD   dwPermission
    )
/*++

Routine Description:

    Open metabase using path & permission
    path is relative to the top of the name space extension ( i.e. /.../<nsepm> )

Arguments:

    pszOpenPath - path to open inside name space extension
    dwPermission - metabase permission ( read/write )

Returns:

    COM status

--*/
{
    HRESULT hres;
    LPWSTR  pszPath;
    UINT    cL = wcslen( m_pszMetabasePath );

    pszPath = (LPWSTR)LocalAlloc( LMEM_FIXED, (wcslen(pszOpenPath) + 1 + cL + 1)*sizeof(WCHAR) );

    if ( pszPath == NULL )
    {
        return E_OUTOFMEMORY;
    }

    memcpy( pszPath, m_pszMetabasePath, cL * sizeof(WCHAR) );
    if ( cL && m_pszMetabasePath[cL-1] != L'/' && *pszOpenPath && *pszOpenPath != L'/' )
    {
        pszPath[cL++] = L'/';
    }
    wcscpy( pszPath + cL, pszOpenPath );

    hres = OpenAdminBaseKey(
                m_pszServerName,
                pszPath,
                dwPermission,
                &m_pcAdmCom,
                &m_hmd
                );

    LocalFree( pszPath );

	if ( FAILED(hres) )
	{
        m_hmd = NULL;
    }

    return hres;
}


HRESULT
CIISDsCrMap::CloseMd(
    BOOL fSave
    )
/*++

Routine Description:

    close metabase

Arguments:

    fSave - TRUE to save data immediatly

Returns:

    COM status

--*/
{
    CloseAdminBaseKey( m_pcAdmCom, m_hmd );
    m_hmd = NULL;
    
    if ( m_pcAdmCom && fSave )
    {
        m_pcAdmCom->SaveData();
    }

    return S_OK;
}


HRESULT
CIISDsCrMap::DeleteMdObject(
    LPWSTR  pszKey
    )
/*++

Routine Description:

    Delete metabase object in an opened tree
    OpenMd() must be called 1st

Arguments:

    pszKey - key to delete in opened metabase

Returns:

    COM status

--*/
{
    return m_pcAdmCom->DeleteKey( m_hmd, pszKey );
}


HRESULT
CIISDsCrMap::CreateMdObject(
    LPWSTR  pszKey
    )
/*++

Routine Description:

    Create metabase object in an opened tree
    OpenMd() must be called 1st

Arguments:

    pszKey - key to create in opened metabase

Returns:

    COM status

--*/
{
    return m_pcAdmCom->AddKey( m_hmd, pszKey );
}


HRESULT
CIISDsCrMap::SetMdData( 
    LPWSTR  achIndex, 
    DWORD   dwProp,
    DWORD   dwDataType,
    DWORD   dwDataLen,
    LPBYTE  pbData 
    )
/*++

Routine Description:

    Set a metabase property
    OpenMd() must be called 1st
    Property will be stored with NULL attribute, except for MD_MAPPWD
     which will be stored with METADATA_SECURE

Arguments:

    achIndex - key name where to store property
    dwProp - property ID
    dwDataType - property data type
    dwDataLen - property length
    pbData - property value

Returns:

    COM status

--*/
{
    METADATA_RECORD     md;

    md.dwMDDataLen = dwDataLen;
    md.dwMDDataType = dwDataType;
    md.dwMDIdentifier = dwProp;
    md.dwMDAttributes = (dwProp == MD_MAPPWD) ? METADATA_SECURE : 0;
    md.pbMDData = pbData;

    return m_pcAdmCom->SetData( m_hmd, achIndex, &md );
}


HRESULT
CIISDsCrMap::GetMdData( 
    LPWSTR  achIndex, 
    DWORD   dwProp,
    DWORD   dwDataType,
    LPDWORD pdwDataLen,
    LPBYTE* ppbData 
    )
/*++

Routine Description:

    Get a metabase property
    OpenMd() must be called 1st

Arguments:

    achIndex - key name where to get property
    dwProp - property ID
    dwDataType - property data type
    pdwDataLen - property length
    ppData - property value, to be freed using LocalFree() on successfull return

Returns:

    COM status

--*/
{
    HRESULT             hres;
    METADATA_RECORD     md;
    DWORD               dwRequired;

    md.dwMDDataLen = 0;
    md.dwMDDataType = dwDataType;
    md.dwMDIdentifier = dwProp;
    md.dwMDAttributes = 0;
    md.pbMDData = NULL;

    if ( FAILED(hres = m_pcAdmCom->GetData( m_hmd, achIndex, &md, &dwRequired )) )
    {
        if ( hres == RETURNCODETOHRESULT(ERROR_INSUFFICIENT_BUFFER) )
        {
            if ( (*ppbData = (LPBYTE)LocalAlloc( LMEM_FIXED, dwRequired )) == NULL )
            {
                return E_OUTOFMEMORY;
            }
            md.pbMDData = *ppbData;
            md.dwMDDataLen = dwRequired;
            hres = m_pcAdmCom->GetData( m_hmd, achIndex, &md, &dwRequired );
            *pdwDataLen = md.dwMDDataLen;
        }
    }
    else
    {
       *pdwDataLen = 0;
       *ppbData = NULL;
    }

    return hres;
}


//////

HRESULT
CIISDsCrMap::Locate(
    LONG    lMethod,
    VARIANT vKey,
    LPWSTR  pszResKey
    )
/*++

Routine Description:

    Locate a mapping entry based on key
    OpenMd() must be called 1st

Arguments:

    lMethod - method to use for access ( IISMAPPER_LOCATE_BY_* )
    vKey - key to use to locate mapping
    pszResKey - 

Returns:

    COM status

--*/
{
    HRESULT     hres;
    LPSTR       pV = NULL;
    DWORD       cV;
    DWORD       dwProp;
    LPSTR       pRes;
    DWORD       cRes;
    BOOL        fAddDelim = TRUE;

    //
    // determine method
    //

    switch ( lMethod )
    {
        case IISMAPPER_LOCATE_BY_CERT:
            dwProp = MD_NSEPM_ACCESS_CERT;
            fAddDelim = FALSE;
            break;

        case IISMAPPER_LOCATE_BY_NAME:
            dwProp = MD_NSEPM_ACCESS_NAME;
            break;

        case IISMAPPER_LOCATE_BY_ACCT:
            dwProp = MD_NSEPM_ACCESS_ACCOUNT;
            break;

        case IISMAPPER_LOCATE_BY_INDEX:
            if ( SUCCEEDED( hres = GetStringFromVariant( &vKey, &pV, &cV, TRUE ) ) )
            {
                WCHAR       pwV[LOCAL_MAX_SIZE]; 
                int i = MultiByteToWideChar(CP_ACP, 0, pV, cV, pwV, LOCAL_MAX_SIZE);
                
                if (i ==0) 
                    return E_FAIL;  // MultiByteToWideChar failure
                if (i >= (int)(LOCAL_MAX_SIZE - wcslen(L"mappings/"))) 
                    return E_FAIL;  //pwV is too big for pszResKey
                
                wsprintfW( pszResKey, L"mappings/%s", pwV );
            }
            goto Exit;

        default:
            return E_FAIL;
    }

    //
    // get ptr to data
    //

    if ( SUCCEEDED( hres = GetStringFromVariant( &vKey, &pV, &cV, fAddDelim ) ) )
    {
        //
        // set search prop, get result
        //

        if ( SUCCEEDED( hres = SetMdData( L"", dwProp, BINARY_METADATA, cV, (LPBYTE)pV ) ) )
        {
            if ( SUCCEEDED( hres = GetMdData( L"", dwProp, DWORD_METADATA, &cRes, (LPBYTE*)&pRes ) ) )
            {
                if ( cRes == sizeof(DWORD ) )
                {
                    wsprintfW( pszResKey, L"mappings/%u", *(LPDWORD)pRes );
                }
                else
                {
                    hres = E_FAIL;
                }
                LocalFree( pRes );
            }
        }
    }

Exit:

    FreeString( pV );

    return hres;
}


HRESULT
GetStringFromBSTR( 
    BSTR    bstr,
    LPSTR*  psz,
    LPDWORD pdwLen,
    BOOL    fAddDelimInCount
    )
/*++

Routine Description:

    Allocate string buffer from BSTR

Arguments:

    bstr - bstr to convert from
    psz - updated with ptr to buffer, to be freed with FreeString()
    pdwLen - updated with strlen(string), incremented by 1 if fAddDelimInCount is TRUE
    fAddDelimInCount - TRUE to increment *pdwLen 

Returns:

    COM status

--*/
{
    UINT    cch = SysStringLen(bstr);
    UINT    cchT;

    // include NULL terminator

    *pdwLen = cch + (fAddDelimInCount ? 1 : 0);

	CHAR *szNew = (CHAR*)LocalAlloc( LMEM_FIXED, (2 * cch) + 1);			// * 2 for worst case DBCS string
	if (szNew == NULL)
    {
		return E_OUTOFMEMORY;
    }

	cchT = WideCharToMultiByte(CP_ACP, 0, bstr, cch + 1, szNew, (2 * cch) + 1, NULL, NULL);

	*psz = szNew;

	return NOERROR;
}


HRESULT
GetStringFromVariant( 
    VARIANT*    pVar,
    LPSTR*      psz,
    LPDWORD     pdwLen,
    BOOL        fAddDelim
    )
/*++

Routine Description:

    Allocate string buffer from BSTR

Arguments:

    pVar - variant to convert from. Recognizes BSTR, VT_ARRAY|VT_UI1, ByRef or ByVal
    psz - updated with ptr to buffer, to be freed with FreeString()
    pdwLen - updated with size of input, incremented by 1 if fAddDelimInCount is TRUE
    fAddDelimInCount - TRUE to increment *pdwLen 

Returns:

    COM status

--*/
{
    LPBYTE  pbV;
    UINT    cV;
    HRESULT hres;
    WORD    vt = V_VT(pVar);
    BOOL    fByRef = FALSE;
    VARIANT vOut;

    VariantInit( &vOut );

    if ( vt & VT_BYREF )
    {
        vt &= ~VT_BYREF;
        fByRef = TRUE;
    }

    if ( vt == VT_DISPATCH )
    {
        if ( FAILED(hres = VariantResolveDispatch( &vOut, pVar )) )
        {
            return hres;
        }
        pVar = &vOut;
        vt = V_VT(pVar);
        if ( fByRef = vt & VT_BYREF )
        {
            vt &= ~VT_BYREF;
        }
    }

    // if pVar is BSTR, convert to multibytes

    if ( vt == VT_VARIANT )
    {
        pVar = (VARIANT*)V_BSTR(pVar);
        vt = V_VT(pVar);
        if ( fByRef = vt & VT_BYREF )
        {
            vt &= ~VT_BYREF;
        }
    }

    if ( vt == VT_BSTR )
    {
        hres = GetStringFromBSTR( fByRef ? 
                                    *(BSTR*)V_BSTR(pVar) :
                                    V_BSTR(pVar), 
                                  psz, 
                                  pdwLen,
                                  fAddDelim );
    }
    else if( vt == (VT_ARRAY | VT_UI1) )
    {
        long        lBound, uBound, lItem;
        BYTE        bValue;
        SAFEARRAY*  pSafeArray;

   
        // array of VT_UI1 (probably OctetString)
   
        pSafeArray  = fByRef ? *(SAFEARRAY**)V_BSTR(pVar) : V_ARRAY( pVar );

        hres = SafeArrayGetLBound(pSafeArray, 1, &lBound);
        hres = SafeArrayGetUBound(pSafeArray, 1, &uBound);

        cV = uBound - lBound + 1;

        if ( !(pbV = (LPBYTE)LocalAlloc( LMEM_FIXED, cV )) )
        {
            hres = E_OUTOFMEMORY;
            goto Exit;
        }

        hres = S_OK;

        for( lItem = lBound; lItem <= uBound ; lItem++ )
        {
            hres  = SafeArrayGetElement( pSafeArray, &lItem, &bValue );
            if( FAILED( hres ) )
            {
                break;
            }
            pbV[lItem-lBound] = bValue;
        }

        *psz = (LPSTR)pbV;
        *pdwLen = cV;
    }
    else if( vt == (VT_ARRAY | VT_VARIANT) )
    {
        long        lBound, uBound, lItem;
        VARIANT     vValue;
        BYTE        bValue;
        SAFEARRAY*  pSafeArray;

   
        // array of VT_VARIANT (probably VT_I4 )
   
        pSafeArray  = fByRef ? *(SAFEARRAY**)V_BSTR(pVar) : V_ARRAY( pVar );

        hres = SafeArrayGetLBound(pSafeArray, 1, &lBound);
        hres = SafeArrayGetUBound(pSafeArray, 1, &uBound);

        cV = uBound - lBound + 1;

        if ( !(pbV = (LPBYTE)LocalAlloc( LMEM_FIXED, cV )) )
        {
            hres = E_OUTOFMEMORY;
            goto Exit;
        }

        hres = S_OK;

        for( lItem = lBound; lItem <= uBound ; lItem++ )
        {
            hres  = SafeArrayGetElement( pSafeArray, &lItem, &vValue );
            if( FAILED( hres ) )
            {
                break;
            }
            if ( V_VT(&vValue) == VT_UI1 )
            {
                bValue = V_UI1(&vValue);
            }
            else if ( V_VT(&vValue) == VT_I2 )
            {
                bValue = (BYTE)V_I2(&vValue);
            }
            else if ( V_VT(&vValue) == VT_I4 )
            {
                bValue = (BYTE)V_I4(&vValue);
            }
            else
            {
                bValue = 0;
            }
            pbV[lItem-lBound] = bValue;
        }

        *psz = (LPSTR)pbV;
        *pdwLen = cV;
    }
    else
    {
        hres = E_FAIL;
    }

Exit:
    VariantClear( &vOut );

    return hres;
}


VOID
FreeString( 
    LPSTR   psz 
    )
/*++

Routine Description:

    Free a string returned by GetStringFromVariant() or GetStringFromBTR()
    can be NULL

Arguments:

    psz - string to free, can be NULL

Returns:

    Nothing

--*/
{
    if ( psz )
    {
        LocalFree( psz );
    }
}


HRESULT
SetBSTR( 
    BSTR*   pbstrRet,
    DWORD   cch, 
    LPBYTE  sz 
    )
/*++

Routine Description:

    Build a BSTR from byte array

Arguments:

    pbstrRet - updated with BSTR
    cch - byte count in sz
    sz - byte array

Returns:

    COM status

--*/
{
	BSTR bstrRet;
	
	if (sz == NULL)
	{
		*pbstrRet = NULL;
		return(NOERROR);
	}
		
	// Allocate a string of the desired length
	// SysAllocStringLen allocates enough room for unicode characters plus a null
	// Given a NULL string it will just allocate the space

	bstrRet = SysAllocStringLen(NULL, cch);
	if (bstrRet == NULL)
    {
		return(E_OUTOFMEMORY);
    }

	// If we were given "", we will have cch=0.  return the empty bstr
	// otherwise, really copy/convert the string
	// NOTE we pass -1 as 4th parameter of MultiByteToWideChar for DBCS support

	if (cch != 0)
	{
		UINT cchTemp = 0;
		if (MultiByteToWideChar(CP_ACP, 0, (LPSTR)sz, -1, bstrRet, cch+1) == 0)
        {
			return(HRESULT_FROM_WIN32(GetLastError()));
        }

		// If there are some DBCS characters in the sz(Input), then, the character count of BSTR(DWORD) is 
		// already set to cch(strlen(sz)) in SysAllocStringLen(NULL, cch), we cannot change the count, 
		// and later call of SysStringLen(bstr) always returns the number of characters specified in the
		// cch parameter at allocation time.  Bad, because one DBCS character(2 bytes) will convert
		// to one UNICODE character(2 bytes), not 2 UNICODE characters(4 bytes).
		// Example: For input sz contains only one DBCS character, we want to see SysStringLen(bstr) 
		// = 1, not 2.

		bstrRet[cch] = 0;
		cchTemp = wcslen(bstrRet);
		if (cchTemp < cch)
		{
			BSTR bstrTemp = SysAllocString(bstrRet);
			SysFreeString(bstrRet);
			bstrRet = bstrTemp;	
			cch = cchTemp;
		}
	}

   if (bstrRet != NULL)
	   bstrRet[cch] = 0;
	*pbstrRet = bstrRet;

	return(NOERROR);
}


HRESULT
CIISDsCrMap::Create(
    IUnknown *pUnkOuter,
    REFIID riid,
    void **ppvObj
    )
/*++

Routine Description:

    Create a CIISDsCrMap

Arguments:

    pUnkOuter - ptr to iunknown
    riid - requested IID
    ppvObj - updated with ptr to requested IID

Returns:

    COM status

--*/
{
    CCredentials Credentials;
    CIISDsCrMap FAR * pMap = NULL;
    HRESULT hr = S_OK;
    BSTR bstrAdsPath = NULL;
    OBJECTINFO ObjectInfo;
    POBJECTINFO pObjectInfo = &ObjectInfo;
    CLexer * pLexer = NULL;
    LPWSTR pszIISPathName = NULL;

    hr = AllocateObject(pUnkOuter, Credentials, &pMap);
    BAIL_ON_FAILURE(hr);

    //
    // get ServerName and pszPath
    //

    hr = pMap->_pADs->get_ADsPath(&bstrAdsPath);
    BAIL_ON_FAILURE(hr);

    pLexer = new CLexer();
    hr = pLexer->Initialize(bstrAdsPath);
    BAIL_ON_FAILURE(hr);

    //
    // Parse the pathname
    //

    memset(pObjectInfo, 0, sizeof(OBJECTINFO));
    hr = ADsObject(pLexer, pObjectInfo);
    BAIL_ON_FAILURE(hr);

    pszIISPathName = AllocADsStr(bstrAdsPath);
    if (!pszIISPathName) {
        hr = E_OUTOFMEMORY;
        BAIL_ON_FAILURE(hr);
    }

    *pszIISPathName = L'\0';
    hr = BuildIISPathFromADsPath(
                    pObjectInfo,
                    pszIISPathName
                    );
    BAIL_ON_FAILURE(hr);

    hr = pMap->Init( pObjectInfo->TreeName,
                     pszIISPathName );
    BAIL_ON_FAILURE(hr);

    //
    // pass non-delegating IUnknown back to the aggregator
    //

    *ppvObj = (INonDelegatingUnknown FAR *) pMap;

    if (bstrAdsPath) {
        ADsFreeString(bstrAdsPath);
    }

    if (pLexer) {
        delete pLexer;
    }

    if (pszIISPathName ) {
        FreeADsStr( pszIISPathName );
    }

    FreeObjectInfo( &ObjectInfo );

    RRETURN(hr);

error:

    if (bstrAdsPath) {
        ADsFreeString(bstrAdsPath);
    }

    if (pLexer) {
        delete pLexer;
    }

    if (pszIISPathName ) {
        FreeADsStr( pszIISPathName );
    }

    FreeObjectInfo( &ObjectInfo );

    *ppvObj = NULL;

    delete pMap;

    RRETURN(hr);
}


STDMETHODIMP
CIISDsCrMap::QueryInterface(
    REFIID iid,
    LPVOID FAR* ppv
    )
/*++

Routine Description:

    Query interface to CIISDsCrMap

Arguments:

    iid - requested IID
    ppv - updated with ptr to requested IID

Returns:

    COM status

--*/
{
    HRESULT hr = S_OK;

    hr = _pUnkOuter->QueryInterface(iid,ppv);

    RRETURN(hr);
}


CIISDsCrMap::CIISDsCrMap(
    )
/*++

Routine Description:

    CIISDsCrMap constructor

Arguments:

    pADs - ptr to contained ADs
    Credentials - credential
    pDispMgr - ptr to dispatch manager

Returns:

    Nothing

--*/
{ 
    m_pcAdmCom = NULL; 
    m_hmd = NULL; 
    m_pszServerName = NULL; 
    m_pszMetabasePath = NULL; 
    m_ADsPath = NULL; 
    _pADs = NULL; 
    _pDispMgr = NULL; 
    ENLIST_TRACKING(CIISDsCrMap);
}


CIISDsCrMap::~CIISDsCrMap(
    )
/*++

Routine Description:

    CIISDsCrMap destructor

Arguments:

    None

Returns:

    Nothing

--*/
{
    if ( m_ADsPath ) 
    {
        ADsFreeString( m_ADsPath );
    }

    if ( m_pszServerName ) 
    {
        LocalFree( m_pszServerName );
    }

    if ( m_pszMetabasePath )
    {
        LocalFree( m_pszMetabasePath );
    }

    if ( _pDispMgr )
    {
        delete _pDispMgr;
    }
}


HRESULT
CIISDsCrMap::AllocateObject(
    IUnknown *pUnkOuter,
    CCredentials& Credentials,
    CIISDsCrMap ** ppMap
    )
/*++

Routine Description:

    Allocate CIISDsCrMap

Arguments:

    pUnkOuter - ptr to iunknown
    Credentials - credential
    ppMap - updated with ptr to IUnknown to Allocated object

Returns:

    COM status

--*/
{
    CIISDsCrMap FAR * pMap = NULL;
    IADs FAR * pADs = NULL;
    CAggregateeDispMgr FAR * pDispMgr = NULL;
    HRESULT hr = S_OK;

    pDispMgr = new CAggregateeDispMgr;
    if (pDispMgr == NULL) {
        hr = E_OUTOFMEMORY;
    }
    BAIL_ON_FAILURE(hr);

    pMap = new CIISDsCrMap();
    if (pMap == NULL) {
        hr = E_OUTOFMEMORY;
    }
    BAIL_ON_FAILURE(hr);

    hr = pDispMgr->LoadTypeInfoEntry(
                LIBID_IISExt,   //LIBID_ADs,
                IID_IISDsCrMap,
                (IISDsCrMap *)pMap,
                DISPID_REGULAR
                );
    BAIL_ON_FAILURE(hr);

    //
    // Store the IADs Pointer, but again do NOT ref-count
    // this pointer - we keep the pointer around, but do
    // a release immediately.
    //

    hr = pUnkOuter->QueryInterface(IID_IADs, (void **)&pADs);
    pADs->Release();
    pMap->_pADs = pADs;

    //
    // Store the pointer to the pUnkOuter object
    // AND DO NOT add ref this pointer
    //

    pMap->_pUnkOuter = pUnkOuter;

    pMap->m_Credentials = Credentials;
    pMap->_pDispMgr = pDispMgr;
    *ppMap = pMap;

    RRETURN(hr);

error:
    delete  pDispMgr;
    delete  pMap;

    RRETURN(hr);
}


HRESULT
CIISDsCrMap::Init( 
    LPWSTR  pszServerName, 
    LPWSTR  pszMetabasePath 
    )
/*++

Routine Description:

    Initialize CIISDsCrMap

Arguments:

    pszServerName - target computer name for metabase access
    pszParent - metabase path to IisMapper object

Returns:

    COM status

--*/
{
    UINT cL;

    cL = wcslen( pszServerName );
    if ( m_pszServerName = (LPWSTR)LocalAlloc( LMEM_FIXED, ( cL + 1 )*sizeof(WCHAR) ) )
    {
        memcpy( m_pszServerName, pszServerName, ( cL + 1 )*sizeof(WCHAR) );
    }
    else
    {
        return E_OUTOFMEMORY;
    }

    cL = wcslen( pszMetabasePath );
    while ( cL && pszMetabasePath[cL-1] != L'/' && pszMetabasePath[cL-1] != L'\\' )
    {
        --cL;
    }
    if ( m_pszMetabasePath = (LPWSTR)LocalAlloc( LMEM_FIXED, ( cL*sizeof(WCHAR) + sizeof(L"<nsepm>") )) )
    {
        memcpy( m_pszMetabasePath, pszMetabasePath, cL * sizeof(WCHAR) );
        memcpy( m_pszMetabasePath + cL, L"<nsepm>", sizeof(L"<nsepm>") );
    }
    else
    {
        return E_OUTOFMEMORY;
    }

	return InitServerInfo(pszServerName, &m_pcAdmCom);
}


HRESULT
SetVariantAsByteArray(
    VARIANT*    pvarReturn, 
    DWORD       cbLen,
    LPBYTE      pbIn 
    )
/*++

Routine Description:

    Create variant as byte array

Arguments:

    pVarReturn - ptr to created variant
    cbLen - byte count
    pbIn - byte array

Returns:

    COM status

--*/
{
	HRESULT         hr;
	SAFEARRAYBOUND  rgsabound[1];
	BYTE *          pbData = NULL;

	// Set the variant type of the output parameter

	V_VT(pvarReturn) = VT_ARRAY|VT_UI1;
	V_ARRAY(pvarReturn) = NULL;

	// Allocate a SafeArray for the data

	rgsabound[0].lLbound = 0;
	rgsabound[0].cElements = cbLen;

	V_ARRAY(pvarReturn) = SafeArrayCreate(VT_UI1, 1, rgsabound);
	if (V_ARRAY(pvarReturn) == NULL)
	{
		return E_OUTOFMEMORY;
	}

	if (FAILED(SafeArrayAccessData(V_ARRAY(pvarReturn), (void **) &pbData)))
	{
		return E_UNEXPECTED;
	}

	memcpy(pbData, pbIn, cbLen );

	SafeArrayUnaccessData(V_ARRAY(pvarReturn));

	return NOERROR;
}


HRESULT
SetVariantAsBSTR(
    VARIANT*    pvarReturn, 
    DWORD       cbLen,
    LPBYTE      pbIn 
    )
/*++

Routine Description:

    Create variant BSTR

Arguments:

    pVarReturn - ptr to created variant
    cbLen - byte count
    pbIn - byte array

Returns:

    COM status

--*/
{
	HRESULT         hr;

    V_VT(pvarReturn) = VT_BSTR;
    return SetBSTR( &V_BSTR(pvarReturn), cbLen, pbIn );
}


HRESULT
SetVariantAsLong(
    VARIANT*    pvarReturn, 
    DWORD       dwV
    )
/*++

Routine Description:

    Create variant as long

Arguments:

    pVarReturn - ptr to created variant
    dwV - value

Returns:

    COM status

--*/
{
	HRESULT         hr;

    V_VT(pvarReturn) = VT_I4;
    V_I4(pvarReturn) = dwV;

    return S_OK;
}


HRESULT 
VariantResolveDispatch(
    VARIANT *   pVarOut, 
    VARIANT *   pVarIn
    )
/*++

Routine Description:

    Extract value from IDispatch default property

Arguments:

    pVarOut - ptr to created variant
    pVarIn - ptr to IDispatch variant to resolve

Returns:

    COM status

--*/
{
	VARIANT		varResolved;		// value of IDispatch::Invoke
	DISPPARAMS	dispParamsNoArgs = {NULL, NULL, 0, 0}; 
	EXCEPINFO	ExcepInfo;
	HRESULT		hrCopy;


	VariantInit(pVarOut);

	hrCopy = VariantCopy(pVarOut, pVarIn);

	if (FAILED(hrCopy))
	{
		return hrCopy;
	}

	// follow the IDispatch chain.
	//
	while (V_VT(pVarOut) == VT_DISPATCH)
	{
		HRESULT hrInvoke = S_OK;

		// If the variant is equal to Nothing, then it can be argued
		// with certainty that it does not have a default property!
		// hence we return DISP_E_MEMBERNOTFOUND for this case.
		//
		if (V_DISPATCH(pVarOut) == NULL)
        {
			hrInvoke = DISP_E_MEMBERNOTFOUND;
        }
		else
		{
			VariantInit(&varResolved);
			hrInvoke = V_DISPATCH(pVarOut)->Invoke(
                            DISPID_VALUE,
                            IID_NULL,
                            LOCALE_SYSTEM_DEFAULT,
                            DISPATCH_PROPERTYGET | DISPATCH_METHOD,
                            &dispParamsNoArgs,
                            &varResolved,
                            &ExcepInfo,
                            NULL);
		}

		if (FAILED(hrInvoke))
		{
			if (hrInvoke == DISP_E_EXCEPTION)
			{			
				//
				// forward the ExcepInfo from Invoke to caller's ExcepInfo
				//
				SysFreeString(ExcepInfo.bstrHelpFile);
			}

			VariantClear(pVarOut);
			return hrInvoke;
		}

		// The correct code to restart the loop is:
		//
		//		VariantClear(pVar)
		//		VariantCopy(pVar, &varResolved);
		//		VariantClear(&varResolved);
		//
		// however, the same affect can be achieved by:
		//
		//		VariantClear(pVar)
		//		*pVar = varResolved;
		//		VariantInit(&varResolved)
		//
		// this avoids a copy.  The equivalence rests in the fact that
		// *pVar will contain the pointers of varResolved, after we
		// trash varResolved (WITHOUT releasing strings or dispatch
		// pointers), so the net ref count is unchanged. For strings,
		// there is still only one pointer to the string.
		//
		// NOTE: the next interation of the loop will do the VariantInit.
		//
		VariantClear(pVarOut);
		*pVarOut = varResolved;
	}

	return S_OK;
}

STDMETHODIMP
CIISDsCrMap::NonDelegatingQueryInterface(
    REFIID iid,
    LPVOID FAR* ppv
    )
{
    ASSERT(ppv);

    if (IsEqualIID(iid, IID_IISDsCrMap)) {

        *ppv = (IADsUser FAR *) this;

    } else if (IsEqualIID(iid, IID_IADsExtension)) {

        *ppv = (IADsExtension FAR *) this;

    } else if (IsEqualIID(iid, IID_IUnknown)) {

        //
        // probably not needed since our 3rd party extension does not stand
        // alone and provider does not ask for this, but to be safe
        //
        *ppv = (INonDelegatingUnknown FAR *) this;

    } else {

        *ppv = NULL;
        return E_NOINTERFACE;
    }


    //
    // Delegating AddRef to aggregator for IADsExtesnion and IISDsCrMap.
    // AddRef on itself for IPrivateUnknown.   (both tested.)
    //

    ((IUnknown *) (*ppv)) -> AddRef();

    return S_OK;
}



//
// IADsExtension::Operate()
//

STDMETHODIMP
CIISDsCrMap::Operate(
    THIS_ DWORD dwCode,
    VARIANT varUserName,
    VARIANT varPassword,
    VARIANT varFlags
    )
{
    RRETURN(E_NOTIMPL);
}

STDMETHODIMP
CIISDsCrMap::ADSIInitializeDispatchManager(
    long dwExtensionId
    )
{
    HRESULT hr = S_OK;

    if (_fDispInitialized) {

        RRETURN(E_FAIL);
    }

    hr = _pDispMgr->InitializeDispMgr(dwExtensionId);

    if (SUCCEEDED(hr)) {
        _fDispInitialized = TRUE;
    }

    RRETURN(hr);
}


STDMETHODIMP
CIISDsCrMap::ADSIInitializeObject(
    THIS_ BSTR lpszUserName,
    BSTR lpszPassword,
    long lnReserved
    )
{
    CCredentials NewCredentials(lpszUserName, lpszPassword, lnReserved);

    m_Credentials = NewCredentials;

    RRETURN(S_OK);
}


STDMETHODIMP
CIISDsCrMap::ADSIReleaseObject()
{
    delete this;
    RRETURN(S_OK);
}