active directory adsi com and c

Tue, Apr 28, 2009

Well here I am, back in my Alma Mater, Windows, working on user account creation for Active Directory. The project I’ve called GADfly (Groupwise Active Directory on the fly). GADfly requires developing on Windows. Well it does and it doesn’t. I’ve already written a couple of Java creators to compare direct LDAPS and TLS which can run from any OS but I’ll need to create home directories and assign correct permissions, which requires ADSI, which is C++ and COM, which is Windows only.

So what does it require to create accounts in Active Directory? The biggest problem I’ve encountered is setting the password on the new user object. That requires 128 bit SSL, quoted and UNICODE. The last two are fine. It’s the first two that cause the problem in ADSI. In Java it’s fine as I can create my own SSLContext and verify the server certificate myself. However, ADSI uses LDAPS on top of SChannel, which doesn’t let you get near the connection. How do I know this? I kept getting the error:

The network path could not be found
which I finally tracked down to schannel not trusting the DC’s certificate. I found this from Event Viewer which you get to from:
Start -> Control Panel -> Administrative Tools -> Event Viewer
and here’s the schannel error:

schannel certificate error

Pretty obvious and what I suspected. Here’s the code I was using to connect to the DC:

hr = ADsOpenObject(L”LDAP://dctest.work.ac.uk/ou=students,dc=test,dc=local”,
                   L”ROOT\USERNAME”, L”PASSWORD”,
                   ADS_SECURE_AUTHENTICATION,
                   IID_IADsContainer,
                   (void**)&pCont);
The machine is not in the AD environment so it needs to connect using ADsOpenObject with username and password. That was fine and creating the user was fine. The error was being thrown on:
IADsUser::SetPassword
After some help from a colleague who knows AD inside out, this is what I did to solve it. First, you need the root cert of the DC you’re connecting to. The DC will have a cert of its own, in this case for dctest.dc.local. Note that the certificate subject doesn’t match the hostname I’m connecting to. It’s signed by a CA, in this case DCTEST-CA.local. So you need the DCTEST-CA.local certificate. .cer format is fine. You then need to load up Console1:
Start -> Run -> mmc
when it starts, you need to enable Certificates:

[flickr:http://farm4.static.flickr.com/3601/3482147105_7edcca309a.jpg?v=0|http://www.flickr.com/photos/ebothy/3482147105/|Enable Certificates in Console1 (mmc)|Enable Certificates in Console1 (mmc)]

[flickr:http://farm4.static.flickr.com/3658/3482147135_3f9df69683.jpg?v=0|http://www.flickr.com/photos/ebothy/3482147135/|Enable Certificates in Console1 (mmc)|Enable Certificates in Console1 (mmc)]

then import the root DC’s CA cert, from DCTEST-CA.local:

Console1 (mmc) import trusted root cert

The next step is to add an entry in the Hosts file for the DC that you connect to to create the accounts, that matches the subject of the DC’s cert. schannel needs the cert to be trusted and the outgoing connection to be to the subject of the DC’s certificate.

C:\Windows\system32\drivers\etc\Hosts

10.10.1.199 dctest.dc.local

then change the code to connect to that alias:
hr = ADsOpenObject(L”LDAP://dctest.dc.local/ou=students,dc=test,dc=local”,
                   L”ROOT\USERNAME”, L”PASSWORD”,
                   ADS_SECURE_AUTHENTICATION,
                   IID_IADsContainer,
                   (void**)&pCont);
SetPassword will work just fine now. Also note that you don’t have to bother with quoting it and making sure it’s UNICODE as ADSI will do this for you. You just call SetPassword.

That’s the boring bit out of the way. How do you actually use ADSI to create an account in Active Directory? Let’s step through it. I won’t bother with releasing interfaces etc, just the basics of COM and querying for the interfaces you need. First, you need to connect to the DC and get a pointer to the IADsContainer interface:

IADsContainer *pCont;
hr = ADsOpenObject(L”LDAP://dctest.dc.local/ou=students,dc=test,dc=local”,
                   L”ROOT\USERNAME”, L”PASSWORD”,
                   ADS_SECURE_AUTHENTICATION,
                   IID_IADsContainer,
                   (void)&pCont);
you then use that interface to create the user object, relative to the connection base. So the following creates the account cn=adsiuser,ou=students,dc=test,dc=local:
IDispatch *pDisp;
pCont->Create(CComBSTR(“user”), CComBSTR(“cn=adsiuser”), &pDisp);
That creates the object and gives you a pointer to the IDispatch interface, which you query to get down to the nitty gritty level of manipulating the new user object:
IADsUser *pUser;
pDisp->QueryInterface(IID_IADsUser, (void)&pUser);
then create the samAccountName to make it a real user:
VARIANT var;
BSTR prop = SysAllocString(L”samAccountName”);
VariantInit(&var);
var.vt = VT_BSTR;
var.bstrVal = SysAllocString(L”adsiuser”);
hr = pUser->Put(prop, var);
SysFreeString(prop);
VariantClear(&var);
VARIANTs are weird. They’re basically bit buckets that can hold almost any kind of type. Welcome to COM! The next step is setting the password for the new user:
BSTR password = SysAllocString(L”hohoho”);
pUser->SetPassword(password);
SysFreeString(password);
pUser->SetInfo();
and the final step is to enable the account:
var.vt=VT_BOOL;
var.boolVal = 0;
pUser->put_AccountDisabled(0);
pUser->SetInfo();
VariantClear(&var);
That’s the account enabled and ready to use. Of course, you’ll need to set many more properties of the user but that’s a bare bones example, omitting the error checking and releasing of interfaces once you’re done with them. ADSI, C++ and COM. A whole new world opens up!

comments powered by Disqus