Active Directory is at its heart LDAP and Kerberos on steroids. For what I’m concerned with in regard to *nix hosts that’s it. On the host it’s nss_ldap for user info and MIT or Heimdal kerberos for authentication like it would be in a pure *nix environment. The only need for Samaba is for the simplicity of adding hosts to AD and managing the kerberos keytab file. The net command fills the role of kadmin. All that could even be done using a Windows hosts and transferring a keytab file to a *nix host to eliminate Samba if you really wanted to do the extra work. While working with Apache’s mod_authnz_ldap and and mod_authz_svn I’ve run into an idiosyncrasy with user primary groups which is a result of how AD stores that information differently from a traditional OpenLDAP setup.
AD stores user group info with both the group and the user. On the group, user DNs are stored in the member attribute. Using Python:
>>> import ldap, pprint
>>> l = ldap.initialize('ldap://example.com')
>>> l.simple_bind_s('[email protected]', '********')
>>> pprint.pprint(l.search_s('ou=groups,dc=example,dc=com', ldap.SCOPE_SUBTREE, 'cn=Domain Admins', ['member']))
[('CN=Domain Admins,OU=Groups,DC=example,DC=com',
{'member': ['CN=TMCLAUGHLIN,CN=Users,DC=example,DC=com',...]})]
On the user, supplemental group DNs are stored in the memberOf attribute while their primary group in the primaryGroupID attribute with the group’s Windows RID value.
>>> pprint.pprint(l.search_s('cn=users,dc=example,dc=com', ldap.SCOPE_SUBTREE, 'sAMAccountName=tmclaughlin', ['memberOf', 'primaryGroupID']))
[('CN=TMCLAUGHLIN,CN=Users,DC=example,DC=com',
{'memberOf': ['CN=Radius Admins,OU=Groups,DC=example,DC=com',
'CN=Domain Server Admin,OU=Groups,DC=example,DC=com',
'CN=Employee,OU=Groups,DC=example,DC=com',
'CN=Schema Admins,OU=Groups,DC=example,DC=com',
'CN=Domain Admins,OU=Groups,DC=example,DC=com',
'CN=Enterprise Admins,OU=Groups,DC=example,DC=com'],
'primaryGroupID': ['513']}
)]
It’s easy enough to search and find a user’s supplemental groups but their primary group is a little harder. The RID is the last component of the group’s SID. (See Windows SID Structure for further explanation of SIDs and their components.) The RID is not stored as a separate attribute of the group but is contained in the group’s objectSid attribute which stores the group’s SID in a binary encoded form.
>>> pprint.pprint(l.search_s('ou=groups,dc=example,dc=com', ldap.SCOPE_SUBTREE, 'cn=Domain Admins', ['objectSid']))
[('CN=Domain Admins,OU=Groups,DC=example,DC=com',
{'objectSid': ['\x01\x05\x00\x00\x00\x00\x00\x05\x15\x00\x00\x007~;e\xa0\x07\xe4I\x18IF\x17\x00\x02\x00\x00']})]
In order to find the user’s primary group you need to first determine the domain SID, then convert it to a string, and finally search for the <domain SID>-<group RID> value. I wasn’t sure how to convert the SID value stored in AD to a string but with some help from web2ldap I was able to see how to do it:
def sid2str(self,sid):
srl = ord(sid[0])
number_sub_id = ord(sid[1])
iav = struct.unpack('!Q','\x00\x00'+sid[2:8])[0]
sub_ids = [
struct.unpack('<I',sid[8+4*i:12+4*i])[0]
for i in range(number_sub_id)
]
return 'S-%d-%d-%s' % (
srl,
iav,
'-'.join([str(s) for s in sub_ids]),
)
The result is the following steps:
# Get RID of primary group
>>> pri_grp_rid = l.search_s('cn=users,dc=example,dc=com', ldap.SCOPE_SUBTREE, 'sAMAccountName=tmclaughlin', ['primaryGroupID'])[0][1]['primaryGroupID'][0]
# Get domain SID
>>> domain_sid = l.search_s('dc=example,dc=com', ldap.SCOPE_BASE)[0][1]['objectSid'][0]
# Convert domain SID to string form
>>> domain_sid_s = sid2str(domain_sid)
# Search for group with <domain SID>-<group RID> objectSid value
>>> pprint.pprint(l.search_s('ou=groups,dc=example,dc=com', ldap.SCOPE_SUBTREE, 'objectSid=%s-%s' % (domain_sid_s, pri_grp_rid), ['cn']))
[('CN=Domain Users,OU=Groups,DC=example,DC=com',
{'cn': ['Domain Users']})]
Thanks for posting the algorithm. Here is a PHP implementation (note that PHP pack/unpack do not support 64-bit int, so this uses bcmath):
function sid2str($sid)
{
$srl = ord($sid[0]);
$number_sub_id = ord($sid[1]);
$x = substr($sid,2,6);
$h = unpack(‘N’,”\x0\x0″.substr($x,0,2));
$l = unpack(‘N’,substr($x,2,6));
$iav = bcadd(bcmul($h[1],bcpow(2,32)),$l[1]);
for ($i=0; $i<$number_sub_id; $i++)
{
$sub_id = unpack('V', substr($sid, 8+4*$i, 4));
$sub_ids[] = $sub_id[1];
}
return sprintf('S-%d-%d-%s', $srl, $iav, implode('-',$sub_ids));
}
function get_primary_group($server, $admin, $passwd, $base, $username)
{
global $server, $admin, $passwd, $connect, $r;
$connect = ldap_connect($server);
ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($connect, LDAP_OPT_REFERRALS, 0);
$bind = ldap_bind($connect, $admin, $passwd) or exit('bind');
$r = ldap_search($connect, $base, "sAMAccountName=$username", array('primaryGroupID')) or exit('ldap_search');
$data = ldap_get_entries($connect, $r);
$pri_grp_rid = $data[0]['primarygroupid'][0];
print("pri_grp_rid = ${pri_grp_rid}\n");
$r = ldap_read($connect, $base, '(objectclass=*)', array('objectSid')) or exit('ldap_search');
$data = ldap_get_entries($connect, $r);
$domain_sid = $data[0]['objectsid'][0];
$domain_sid_s = sid2str($domain_sid);
print("domain_sid_s = ${domain_sid_s}\n");
$r = ldap_search($connect, $base, "objectSid=${domain_sid_s}-${pri_grp_rid}", array('cn')) or exit('ldap_search');
$data = ldap_get_entries($connect, $r);
print("cn = {$data[0]['cn'][0]}\n");
}
Comment by Chris — October 5, 2010 @ 8:14 am
Related.. Trackback…
[...]the time to read or visit the content or sites we have linked to below the[...]…
Trackback by Buy Guaranteed Facebook Fans — December 2, 2011 @ 3:56 pm