Directory Services

Enabling Service Account to Access SCP Properties

The following code example sets a pair of Access Control Entries (ACEs) on a service connection point (SCP) object. The ACEs grant read/write access to the user or computer account under which the service instance will be running. The service installer uses code similar to the following to ensure that the service can update its properties at run time. If ACEs similar to these these are not set, the service will not have access to the properties of the SCP.

Typically, a service installer will set these ACEs after creating the SCP object. For more information, and a code example that creates an SCP and calls this function, see How Clients Find and Use a Service Connection Point. If the service is reconfigured to run under a different account, the ACEs must be updated. To run successfully, this code example must be run in the security context of a domain administrator.

The first parameter of the sample function specifies the name of the user account to be granted access. The function assumes the name is in "<domain>\<username>" format. If no account is specified, the function assumes the service uses the LocalSystem account. This means the function must grant access to the computer account of the host server on which the service is running. To do this, the code example calls the GetComputerObjectName function to obtain the domain and username of the local computer.

The following code example can be modified to grant the service full access to the SCP object, but the best practice is to grant only the specific access rights that the service requires at run time. In this case, the function grants access to two properties.

Property Description
serviceDNSName The name of the host server on which the service is running.
serviceBindingInformation Private binding information that the service updates when it starts.

Each property is identified by the schemaIDGUID of the property's attributeSchema class. Every property in the schema has its own unique schemaIDGUID. The following code example uses strings to specify the GUIDs. The GUID strings have the following format where each "X" is replaced by a hexadecimal digit.

{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}

Refer to the Active Directory Schema reference pages for the schemaIDGUID values assigned to the properties to grant or deny access to.

The following code example uses the IADsSecurityDescriptor, IADsAccessControlList, and IADsAccessControlEntry interfaces to perform the following operations.

  1. Obtain the security descriptor of the SCP object.
  2. Set the appropriate ACEs in the discretionary access control list (DACL) of the security descriptor.
  3. Modify the security descriptor of the SCP object.
#include <atlbase.h>

//***************************************************************************
//
//  AllowAccessToScpProperties()
//
//***************************************************************************

HRESULT AllowAccessToScpProperties(
	LPWSTR wszAccountSAM,   // Service account to allow access.
	IADs *pSCPObject)	 // IADs pointer to the SCP object.
{
	HRESULT hr = E_FAIL;
	IADsAccessControlList *pACL = NULL;
	IADsSecurityDescriptor *pSD = NULL;
	IDispatch *pDisp = NULL;
	IADsAccessControlEntry *pACE1 = NULL;
	IADsAccessControlEntry *pACE2 = NULL;
	IDispatch *pDispACE = NULL;
	long lFlags = 0L;
	CComBSTR sbstrTrustee;
	CComBSTR sbstrSecurityDescriptor = L"nTSecurityDescriptor";
	VARIANT varSD;

	if(NULL == pSCPObject)
	{
		return E_INVALIDARG;
}

	VariantInit(&varSD);
	 
	// If no service account is specified, service runs under LocalSystem.
	// Allow access to the computer account of the service's host.
	if (wszAccountSAM) 
	{
		sbstrTrustee = wszAccountSAM;
}
	else
	{
		LPWSTR pwszComputerName;
		DWORD dwLen;
	
		// Get the size required for the SAM account name.
		dwLen = 0;
		GetComputerObjectNameW(NameSamCompatible, 
			NULL, &dwLen);
	
		pwszComputerName = new WCHAR[dwLen + 1];
		if(NULL == pwszComputerName)
		{
			hr = E_OUTOFMEMORY;
			goto cleanup;
	}

		// Get the SAM account name of the computer object for the server.
		if(!GetComputerObjectNameW(NameSamCompatible,
			pwszComputerName, &dwLen))
		{
			delete pwszComputerName;
		
			hr = HRESULT_FROM_WIN32(GetLastError());
			goto cleanup;
	}

		sbstrTrustee = pwszComputerName;
		wprintf(L"GetComputerObjectName: %s\n", pwszComputerName);
		delete pwszComputerName;
} 

	// Get the nTSecurityDescriptor.
	hr = pSCPObject->Get(sbstrSecurityDescriptor, &varSD);
	if (FAILED(hr) || (varSD.vt != VT_DISPATCH)) 
	{
		_tprintf(TEXT("Get nTSecurityDescriptor failed: 0x%x\n"), hr);
		goto cleanup;
} 
	 
	// Use the V_DISPATCH macro to get the IDispatch pointer from VARIANT 
	// structure and QueryInterface for an IADsSecurityDescriptor pointer.
	hr = V_DISPATCH( &varSD )->QueryInterface(IID_IADsSecurityDescriptor,
											(void**)&pSD);
	if (FAILED(hr)) 
	{
		_tprintf(TEXT("Cannot get IADsSecurityDescriptor: 0x%x\n"), hr);
		goto cleanup;
} 
	 
	// Get an IADsAccessControlList pointer to the security descriptor's DACL.
	hr = pSD->get_DiscretionaryAcl(&pDisp);
	if (SUCCEEDED(hr))
	{
		hr = pDisp->QueryInterface(IID_IADsAccessControlList,(void**)&pACL);
}
	if (FAILED(hr)) 
	{
		_tprintf(TEXT("Cannot get DACL: 0x%x\n"), hr);
		goto cleanup;
} 
	 
	// Create the COM object for the first ACE.
	hr = CoCreateInstance(CLSID_AccessControlEntry,
						NULL,
						CLSCTX_INPROC_SERVER,
						IID_IADsAccessControlEntry,
						(void **)&pACE1);
	 
	// Create the COM object for the second ACE.
	if (SUCCEEDED(hr))
	{
		hr = CoCreateInstance(CLSID_AccessControlEntry,
						NULL,
						CLSCTX_INPROC_SERVER,
						IID_IADsAccessControlEntry,
						(void **)&pACE2);
}
	if (FAILED(hr)) 
	{
		_tprintf(TEXT("Cannot create ACEs: 0x%x\n"), hr);
		goto cleanup;
} 
	 
	// Set the properties of the two ACEs.
							
	// Allow read and write access to the property.
	hr = pACE1->put_AccessMask( ADS_RIGHT_DS_READ_PROP | ADS_RIGHT_DS_WRITE_PROP );
	hr = pACE2->put_AccessMask( ADS_RIGHT_DS_READ_PROP | ADS_RIGHT_DS_WRITE_PROP );
							
	// Set the trustee, which is either the service account or the 
	// host computer account.
	hr = pACE1->put_Trustee( sbstrTrustee );
	hr = pACE2->put_Trustee( sbstrTrustee );
							
	// Set the ACE type.
	hr = pACE1->put_AceType( ADS_ACETYPE_ACCESS_ALLOWED_OBJECT );
	hr = pACE2->put_AceType( ADS_ACETYPE_ACCESS_ALLOWED_OBJECT );
							
	// Set AceFlags to zero because ACE is not inheritable.
	hr = pACE1->put_AceFlags( 0 );
	hr = pACE2->put_AceFlags( 0 );
	 
	// Set Flags to indicate an ACE that protects a specified object.
	hr = pACE1->put_Flags( ADS_FLAG_OBJECT_TYPE_PRESENT );
	hr = pACE2->put_Flags( ADS_FLAG_OBJECT_TYPE_PRESENT );
	 
	// Set ObjectType to the schemaIDGUID of the attribute.
	hr = pACE1->put_ObjectType( 
			L"{28630eb8-41d5-11d1-a9c1-0000f80367c1}" ); // serviceDNSName
	hr = pACE2->put_ObjectType( 
			L"{b7b1311c-b82e-11d0-afee-0000f80367c1}" ); // serviceBindingInformation
	 
	// Add the ACEs to the DACL. Need an IDispatch pointer for each ACE 
	// to pass to the AddAce method.
	hr = pACE1->QueryInterface(IID_IDispatch,(void**)&pDispACE);
	if (SUCCEEDED(hr))
	{
		hr = pACL->AddAce(pDispACE);
}
	if (FAILED(hr)) 
	{
		_tprintf(TEXT("Cannot add first ACE: 0x%x\n"), hr);
		goto cleanup;
}
	else 
	{
		if (pDispACE)
			pDispACE->Release();

		pDispACE = NULL;
}
	 
	// Repeat for the second ACE.
	hr = pACE2->QueryInterface(IID_IDispatch, (void**)&pDispACE);
	if (SUCCEEDED(hr))
	{
		hr = pACL->AddAce(pDispACE);
}
	if (FAILED(hr)) 
	{
		_tprintf(TEXT("Cannot add second ACE: 0x%x\n"), hr);
		goto cleanup;
}
	 
	// Write the modified DACL back to the security descriptor.
	hr = pSD->put_DiscretionaryAcl(pDisp);
	if (SUCCEEDED(hr))
	{
		// Write the ntSecurityDescriptor property to the property cache.
		hr = pSCPObject->Put(sbstrSecurityDescriptor, varSD);
		if (SUCCEEDED(hr))
		{
			// SetInfo updates the SCP object in the directory.
			hr = pSCPObject->SetInfo();
	}
}
								
	cleanup:
	if (pDispACE)
		pDispACE->Release();
					
	if (pACE1)
		pACE1->Release();
				
	if (pACE2)
		pACE2->Release();
				
	if (pACL)
		pACL->Release();
			 
	if (pDisp)
		pDisp->Release();
		
	if (pSD)
		pSD->Release();
 
	VariantClear(&varSD);
 
	return hr;
}