Linux and Active Directory

Introduction

This note is an account of how one Linux system was configured to allow users to use the same username and password to access Linux based services as they use for accessing Microsoft Windows based services.

There is a separate discussion describing the techniques used to keep the Linux and Active Directory user lists synchronised.

Active Directory

Active Directory (AD) is a name server commonly used on networked PCs using various Microsoft Windows operating systems. It provides the basic infrastructure that allows users to login securely on a shared system. It also provides information about system resources (printers, file system objects etc.).

In the networked context client machines talk to an AD server using various protocols. The basic mechanism is called Kerberos but most AD servers can be accessed using a simpler protocol called Lightweight Directory Access Protocol (LDAP), in spite of this name, it isn't that simple.

Before going further it is also worth noting that AD organises all its information into a hierarchy of items. Leaves in the hierarchy represent things such as machines, users, printers etc.

Since AD is a server, to access it remotely a client programme is needed. Alternatively library routines can be incoporated in applications that need to talk to an AD server.

On a normal Microsoft Windows network set up there will be a primary AD server (known as the PDC or Primary Domain Controller) and a backup AD server (known as the BDC or Backup Domain Controller). If the system is correctly configured changes to the PDC will automatically be propagated to the BDC. If a client cannot contact the PDC it will attempt to contact the BDC. This means, of course, that all clients need to know the addresses of both controllers.

LDAP, AD and Linux

In this discussion the PDC has IP address 192.168.0.100 and the BDC has IP address 192.168.0.101.

LDAP client software may not be part of a standard Linux installation. For this discussion we'll be using the Linux command line tool ldapsearch. To check whether this is part of your Linux installation try the following command.

which ldapsearch

If it says "no ldapsearch ... " then you need to install it. A description of how we did this is available.

Before attempting to access an AD server via LDAP you need a user account known to the AD server. The LDAP standards allow a technique known as "anonymous binding" in which a user trying to access the information stored on an LDAP server doesn't need to supply any identification, however Microsoft AD servers, quite sensibly, disable this facility. A user can, of course, use his own AD user name and password but this is not very convenient. Most AD administrators solve this problem by creating a special user just for use by clients. Our AD administrator was quickly persuaded to create such a user with the user name "ldap-linux" and a suitable password. [The password appears in these notes as "mumble", this is not the actual value.]

The operation of ldapsearch is influenced by default settings in the file /etc/ldap.conf. These typically include the user name and password used for "binding" to the LDAP server.

LDAP hierarchy

Before attempting any queries it is necessary to understand the hierarchy of object types and entries stored in AD as presented to an LDAP client. [Do not confuse the use of the word "object" here with its use in the context of a certain programming style.]

If you are familiar with Microsoft Windows in a networked environment you can view the AD hierarchy graphically via the "My Network Places" icon. You'll see a hierarchy of Domains, Containers, Organizational Units, Users, Groups etc., etc.,. LDAP has a rather different way of talking about such things and exposes detailed information about Users etc., in a rather different way. This will become clearer if you compare the results of the following examples with what you see when you examine the AD hierarchy visually.

Using ldapsearch

It's time to illustrate the use of ldapsearch. Remember these examples are specific to our setup but should provide enough clues to use LDAP in other environments. Here's an attempt to get all the information (attributes) about the "ldap-linux" user from AD.

ldapsearch -x -b "ou=staff,dc=ptwol,dc=net" -D ↵
      "cn=ldap linux,ou=staff,dc=ptwol,dc=net"i↵
      -w Mumble "(cn=ldap linux)"

And here's the output produced by the above command.

# extended LDIF
#
# LDAPv3
# base  with scope subtree
# filter: (cn=ldap linux)
# requesting: ALL
#

# ldap linux, staff, ptwol.net
dn: CN=ldap linux,OU=staff,DC=ptwol,DC=net
accountExpires: 9223372036854775807
badPasswordTime: 128620560680172412
badPwdCount: 0
codePage: 0
cn: ldap linux
countryCode: 0
displayName: ldap linux
givenName: ldap
instanceType: 4
lastLogoff: 0
lastLogon: 128620560706735082
logonCount: 0
distinguishedName: CN=ldap linux,OU=staff,DC=ptwol,DC=net
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=ptwol,DC=net
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
objectGUID:: 6YV8K6kKgE2RAK1bTYTA9w==
objectSid:: AQUAAAAAAAUVAAAA2VR1p5TefIW7GbswfgwAAA==
primaryGroupID: 513
pwdLastSet: 128345150696250000
name: ldap linux
sAMAccountName: ldap-linux
sAMAccountType: 805306368
sn: linux
userAccountControl: 66048
userPrincipalName: ldap-linux@ptwol.net
uSNChanged: 312830
uSNCreated: 301849
whenChanged: 20070917150429.0Z
whenCreated: 20070911111554.0Z

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

That is all the information available from AD, much more than you can normally see via the MS Windows GUI, even if you have administrative privileges but some explanations are clearly called for. Let's start with the parameters of the ldapsearch command.

  1. The parameter -x. This, to quote the manual, means "use simple authentication instead of SASL". If you understand SASL and know whether your insallation supports it, you can try leaving this out.

  2. The parameter -b "ou=staff,dc=ptwol,dc=net". This specifies whereabouts in the hierarchy to start searching, the hierarchy is expressed in an address like fashion with the highest element last. You can leave out the "ou=staff" part and get the same result, only the server will work harder exploring more branches of the hierarchy. The two "dc" (domain component) parts identify the "domain" for which this is the server.

  3. The parameter -D "cn=ldap linux,ou=staff,dc=ptwol,dc=net" identifies the authorising entry (in this case a staff user). In the jargon this is called binding to the directory. Like the previous parameter it identifies a location in the directory hierarchy, unlike the previous parameter you can't sensibly leave parts out. If you have a user registration on the AD server you can replace "ldap linux" by your own user name (but see below).

  4. The parameter -w Mumble is the password associated with the binding user. You can use the parameter -W instead, in which case ldapsearch will prompt the user for the relevant password, this is probably preferable for interactive use.

  5. The parameter "(cn=ldap linux)" is the "filter" associated with the search. This means that information will be displayed for all objects which have a component called "cn" which has the value "ldap linux". Note the use of parentheses and the enclosing double quotes.

You might (should) wonder why the parameters referred to "ldap linux" (without a hyphen) when, earlier, it was stated that the special AD account created was "ldap-linux" (with a hyphen). The answer is that, for the AD account, ldap-linux is the value of an attribute called sAMAccountName which you can think of as your login or account name rather than your "display" name. You can replace "cn=ldap linux" in the filter parameter with "sAMAccountName=ldap-linux". You cannot make the same substitution in the binding parameter ("-D"). The reason is that the binding parameter must identify a unique entry in the directory hierarchy by, effectively, quoting it's distinguished name. The distinguished name of an object is formed from it's common name (cn) and it's position in the hierarchy.

Finally note that ldapsearch has returned all all the attributes for a particular entry. A list of the names of required attributes can be given after the filter. The filter can be omitted if information about all entries beneath the base is required, however if a filter is specified it must be enclosed in parentheses. There is a fairly complex syntax which allows the constructions of complex queries including compound logical queries, unfortunately it doesn't support regular expressions although you can use "*" as wild card for part or all of an attribute value.

Here are some examples of searches. The searches are expressed in terms of filter and list of attributes. The results depend, of course, on the setting of the base parameter.

  1. "(sn=*Jones)" cn sAMAccountName - common name and account name for all users with surname "Jones". This assumes that the sn attribute is used to store surnames.

  2. "(logonCount=0)" cn sAMAccountName - the same information for all users who have never logged on.

  3. "(whenCreated<=20070101000000.0Z)" givenName sn logonCount whenCreated - first name, suranme, logon count and account created time for all accounts created on or before 0000 on 1st January 2007. Note the clumsy date format.

Settings of /etc/ldap.conf

Here is a slightly edited version of the /etc/ldap.conf file on our system. The information in this file provide default settings for utilities such as ldapsearch and applications that use libraries to interrogate AD. Specific command line arguments or application API function parameters can over-ride these settings.

# Your LDAP server. Must be resolvable without using LDAP.
host 192.168.0.100 192.168.0.101
# The distinguished name of the search base.
base ou=staff,dc=ptwol,dc=net
# The LDAP version to use (defaults to 3
ldap_version 3
# The distinguished name to bind to the server with.
# Optional: default is to bind anonymously.
binddn cn=ldap linux,ou=staff,dc=ptwol,dc=net
# The credentials to bind with. 
# Optional: default is no credential.
bindpw mumble

There are many other parameters you can set in /etc/ldap.conf

Hacking the directory

It has already been explained that AD is a hierarchy of entries or objects. You can use a tool such as ldapsearch to explore the hierarchy on your server. Start with the following command.

ldapsearch -x -b "dc=ptwol,dc=net" -D "cn=ldap linux,ou=staff,dc=ptwol,dc=net" ↵
-w mumble -s one dn cn

The -s one parameter tells ldapsearch not to recursively descend the hierarchy as it normally does but just examine entries at the current level. The current level is specified by the "-b" parameter. Trying it on our system identified the following 11 top level entries.

Nametype
Builtincn
Computerscn
Domain Controllersou
Foreign Security Principalscn
Infrastructurecn
LostAndFoundcn
Staffou
Studentou
Systemcn
Userscn
Working Knowledgeou

If you wish you can now draw a tree with 11 branches. The next step is to repeat the command above with the "base" parameter ("-b") specifying one of the branches and repeat the exercise as often as desired. Caution : If you repeat this too often you will end up knowing things about your set up that the AD administrator doesn't know.

A PHP script for user authentication

We use PHP scripts as web back ends quite extensively in order to provide access to corporate systems via the WWW. As part of such provision, it is necessary to check user supplied user names and passwords.

Here is a PHP script including the function validate() which performs the necessary checks.

<?
function	uservalidate($u,$p)
{
/*
	Validates user name and password by talking to Active
	Directory using LDAP.

	Input parameters	$u	User name
				$p	Password

	Return value		A two element associative array
				
				"status"	1 - OK
						2 - Bad Password
						3 - Unknown user name
						4 - Various system errors

				"message"	Descriptive text
*/
	$binduser = "ldap linux";
	$bindpass = "Mumble";
	$basedn = "ou=staff,dc=ptwol,dc=net";
	$ldaphost = "pserver.ptwol.net";
	$ldapconn = @ldap_connect($ldaphost);
	if($ldapconn == FALSE)
	{
		return array(status=>4,message=>"System error 1"); 
	}
	if(@ldap_bind($ldapconn,$binduser,$bindpass) == FALSE)
	{
		return array(status=>4,message=>"System error 2");
	}
	$sr = @ldap_search($ldapconn,$basedn,"sAMAccountName=".$u);
	if($sr == FALSE)
	{
		return array(status=>4,message=>"System error 3");
	}
	$info = ldap_get_entries($ldapconn,$sr);
	$nr = $info["count"];
	if($nr == 0)
		return array(status=>3,message=>"Unknown");
	if($nr > 1)
		return array(status=>4,message=>"System error 4");
	$cn = $info[0]["cn"][0];
	if(@ldap_bind($ldapconn,$cn,$p) == FALSE)
		return array(status=>2,message=>"bad authentication");
	else
		return array(status=>1,message=>"OK");
	
}
?>

If you have followed the notes above and are reasonably familiar with PHP, you should find this fairly straightforward. Note that the PHP LDAP function calls are prefixed with "@", this prevents a failing call generating any diagnostic information that might be useful to an attacker.

The complications reflect the fact that LDAP users, in general, will know their "sAMAccountName" but won't know their "common name", so we have to first bind using a special account for which teh common name and password are known, then use that binding to find the common name associated with the provided username. Only then is it possible to attempt to bind with the provided password and the user's actual "common name".

The error codes and messages have the following meanings.

System error 1Couldn't connect to LDAP server
System error 2Couldn't bind using "ldap linux" common name and password
System error 3Search for user name failed
UnknownThe user name was not in the LDAP database
System error 4The user name occurs more than once in the LDAP database
Bad authenticationThe supplied user name and password don't match information stored in the LDAP database
OKEverything has worked

Author : Peter Burden

Home : Our system setup