Querying LDAP with PHP

ldap-logo

Greetings. So today I’m gonna write about stuff. Managing basic LDAP things with PHP, to be precise… Had a problem at work, that sometimes we do not receive information about new people when they get accepted. Because we don’t receive any information, we don’t always know what to import/create. Then those people come to us asking why they can’t log into computers and use Wifi, then we have to look them up in a outside Oracle DB and create a user like that. That’s a silly, repetitive and a daunting task. Some kinda tool to automate this had to be created so it could be integrated into our existing IT system as well.
I’m not gonna write about the tool, instead I’ll be writing about problems that came up adding users, creating groups and modifying attributes.

To begin with, I worked with Microsoft’s ActiveDirectory on a local server. Tried to install LDAP plugins on localhost so I could develop it from my machine, but that went crashing down, it was relatively easy to install those LDAP plugins though. Anyhow, then I just coded on localhost and when I needed to run the stuff, I transferred that to an LDAP server. Was not cool at first, gotta transfer everything by hand, but then I remembered there is a plugin for Notepad++ called NppFTP which automates that. Transfer everything with an FTP client, connect to that FTP through NppFTP and open documents straight from the FTP window and so the plugin uploads everything automatically on save.

That said, let’s start with actual development. PHP LDAP documentation is pretty much all you need for most of the things, but I had few problems with some of the functions. If you are working with MS’s AD, then this is also useful. Connecting is simple, but if you later need to create users and change passwords, you’ll have to do that over SSL (port 636). Otherwise port 389 is enough.

Connecting and reading is pretty straight-forward once you know how LDAP distinguished names work. You have to get it just right, otherwise all you get is “Invalid Syntax” error without any additional details and there can be various shit wrong with your request…
When I was stuck I resorted to searching for helper projects that would ease my life interacting with LDAP and I found a decent one, called adLDAP – but it was more help for me as a reference than framework and it was a great one.

I’m gonna give you some code snippets for reading first.

  1. private function connect() {
  2.     $tinka = FALSE;
  3.     // go into  a loop, because sometimes the connection breaks for no reason...
  4.     while ($tinka == FALSE) {
  5.         $ldap = ldap_connect($this->config['ldapServers'], 636) or die ("Error trying to connect: ".ldap_errno($ldap)." - ".ldap_error($ldap));
  6.         ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
  7.         ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
  8.  
  9.         // echo "<br/>".$ldap; die();
  10.         $bind = ldap_bind($ldap, $this->config['ldapUsername'], $this->config['ldapPassword']);
  11.  
  12.         // echo $bind; die();
  13.         // var_dump($bind); die();
  14.         if (($bind == 1) && $ldap) {
  15.             return $ldap;
  16.         } else {
  17.             return null;
  18.         }
  19.     }
  20. }

To list login names, you can use this function:

  1. public function getNames($fetchDisabled = true) {
  2.     $ldap = $this->connect();
  3.     $dn = 'OU=Folder, dc=subdomain, dc=domain, dc=com';
  4.     if ($fetchDisabled) { // count everyone
  5.         $results = ldap_search($ldap, $dn, '(objectClass=User)', array('samaccountname'));
  6.     }  else { // omit disabled
  7.         $results = ldap_search($ldap, $dn, '(&(objectClass=User)(userAccountControl=512))', array('samaccountname'));
  8.     }
  9.     $data = ldap_get_entries($ldap, $results);
  10.     // echo "<pre>"; print_r($data); die();
  11.     $naujas = array();
  12.     foreach($data as $key => $reiksme) {
  13.         $entry = $reiksme['samaccountname'][0];
  14.         if (trim($entry) != "") {
  15.             $naujas[] = $entry;
  16.         }
  17.     }
  18.     return $naujas;
  19. }

To check if a person exists by his login name:

  1. public function exists($name) {
  2.     $ldap = $this->connect();
  3.     $dn = 'OU=Folder, dc=subdomain, dc=domain, dc=com';
  4.     $results = ldap_search($ldap, $dn, '(samaccountname='.$name.')', array('samaccountname'));
  5.     return ldap_count_entries($ldap, $results) > 0;
  6. }

To get some summary of any person:

  1. public function getPersonSummary($name) {
  2.     $ldap = $this->connect();
  3.     $dn = 'OU=Folder, dc=subdomain, dc=domain, dc=com';
  4.     $results = ldap_search($ldap, $dn, '(samaccountname='.$name.')', array('samaccountname', 'displayName', 'memberof'));
  5.     $data = ldap_get_entries($ldap, $results);
  6.     // echo "<pre>"; print_r($data); die();
  7.     $summaryArr['login_name'] = $data[0]['samaccountname'][0];
  8.     $summaryArr['name'] = $data[0]['displayname'][0];
  9.     $a = explode(",", $data[0]['memberof'][0]);
  10.     $a = explode("=", $a[0]);
  11.     $summaryArr['group'] = $a[1];
  12.     return $summaryArr;
  13. }

To disable a person, use this. The number can probably either be 546, 2, 514 or 66082 for disabled people. I use 2 and it works for me.

  1. public function disablePerson($name) {
  2.     $dn = 'CN='.$name.', OU=Folder, DC=Subdomain, DC=domain ,DC=com';
  3.     $ldap = $this->connect();
  4.     $personInfo['userAccountControl'] = 2;
  5.     ldap_modify($ldap, $dn, $personInfo);
  6. }

Checking if user is disabled:

  1. private function isUserDisabled($ldap, $studNr) {
  2.     $dn = 'OU=Folder, dc=subdomain, dc=domain, dc=com';
  3.     $results = ldap_list($ldap, $dn, '(samaccountname='.$studNr.')', array('useraccountcontrol'));
  4.     $data = ldap_get_entries($ldap, $results);
  5.     $disabledCodes = array(546, 2, 514, 66082);
  6.     return (in_array($data[0]['useraccountcontrol'][0], $disabledCodes));
  7. }

Deleting a group:

  1. public function deleteGroup($groupName) {
  2.     $dn = 'CN='.$groupName.', OU=GroupFolder, DC=subdomain, DC=domain, DC=com';
  3.     $ldap = $this->connect();
  4.     $r = ldap_delete($ldap, $dn);
  5.     if (!$r) { // someshit failed man!
  6.         echo ldap_error($ldap); die();
  7.     }
  8. }

Renaming a group uses a function that isn’t well documented, however example code in the PHP LDAP docs submitted by other uses were helpful enough:

  1. public function renameGroup($oldGroup, $newGroup) {
  2.     // echo "<pre>"; print_r($newGroup); die();
  3.  
  4.     $ldap = $this->connect();
  5.  
  6.     $dn = 'CN='.$oldGroup.', OU=GroupFolder, DC=subdomain, DC=domain, DC=com';
  7.     // first we replace the attributes of the group
  8.     $grupeArr['samaccountname'] = $newGroup;
  9.     $grupeArr['mail'] = $newGroup.'@ish.ish.com';
  10.     $grupeArr['description'] = $newGroup;
  11.  
  12.     $r = ldap_modify($ldap, $dn, $grupeArr);
  13.     if (!$r) { // someshit failed man!
  14.         echo ldap_error($ldap); die();
  15.     }
  16.     // now we rename the group. Memberof in people profiles changes automatically
  17.     $newDn = 'CN='.$newGroup; // name of our new entry
  18.     $newParent = 'OU=GroupFolder, DC=subdomain, DC=domain, DC=com'; // full DN without the name entry
  19.     $r = ldap_rename($ldap, $dn, $newDn, $newParent, true); // yes, we want to remove the old entry when renaming
  20.     if (!$r) { // someshit failed man!
  21.         echo ldap_error($ldap); die();
  22.     }
  23. }

Now about writing new entries in LDAP. Creating a group is pretty easy. Well, actually it all is pretty easy now that I have it figured out, but before I had it was a bit of a mindfuck creating new people. For groups I use this:

  1. public function createGroup($groupName) {
  2.     $dn = 'CN='.$groupName.', OU=GroupFolder, DC=subdmain, DC=domain, DC=com';
  3.     $ldap = $this->connect();
  4.     $grupeArr['samaccountname'] = $groupName;
  5.     $grupeArr['mail'] = $groupName.'@ish.ish.com';
  6.     $grupeArr['description'] = $groupName;
  7.     $grupeArr['objectclass'] = array('top', 'group');
  8.     $r = ldap_add($ldap, $dn, $grupeArr);
  9.     if (!$r) { // someshit failed man!
  10.         echo ldap_error($ldap); die();
  11.     }
  12. }

When creating a new entry, the “objectclass” attribute has to be specified in full. I suggest looking what values do groups have in the folder you are creating new group so you know what to put there. Otherwise LDAP won’t accept your request. I think “top” is always the first and it goes in everywhere.

Creating a user you gotta get all the fields you use correctly. Character case doesn’t matter and we don’t use a lot of attributes. Debugging was painful but finally got it down :P

  1. $personInfo['samaccountname'] = $row_savnr['ISKKG']; // pre-win2k user
  2. $personInfo['userPrincipalName'] = $row_savnr['ISKKG'].'@subdomain.domain.com'; // user name for logging in
  3. $personInfo['givenName'] = $vardPavard[1]; // name
  4. $personInfo['sn'] = $vardPavard[0]; // lastname
  5. $personInfo['displayName'] = $row_savnr['PAVAR']; // how the name will be displayed once this user logs in
  6. $personInfo['description'] = $row_savnr['GRUPE']; // obviously a description...
  7. $personInfo['homeDrive'] = 'H:'; // network drive mount point
  8. $personInfo['homeDirectory'] = '\\\\servername\\userdata$\\'.$row_savnr['ISKKG'];
  9. $personInfo['profilePath'] = '\\\\servername\\profiles$\\'.$row_savnr['ISKKG'];
  10. $personInfo['scriptPath'] = 'rmrf.bat';
  11. $personInfo['mail'] = $row_savnr['ISKKG'].'@subdomain.domain.com'; // work mail with username
  12. $personInfo['wwwHomePage'] = strtolower($this->makePlain($vardPavard[1]).'.'.$this->makePlain($vardPavard[0]).'@subdomain.domain.com'); // also work mail with name.lastname, must be plaintext (not Unicode) though
  13. $personInfo['objectclass'] = array('top', 'person', 'organizationalPerson', 'user'); // class of this object/user. Look this up from others already created if you can, if not then ask your angry sysadmin :P
  14. $personInfo['unicodePwd'] = $this->encodePassword('supuhpasswdover9000'); // has to be specially encoded, I don't think you can use a Unicode passwd with this field, even though it is named UnicodePwd...
  15. $personInfo['userAccountControl'] = 512; // normal account, without this bit set, our user will be created as locked out
  16. $personInfo['pwdlastset'] = 0; // with this bit the user will have to change his passwd on next logon
  17. $personInfo['telephoneNumber'] = $row_savnr['STELEF'];

And of course the password has to be encoded in a special/weird way, this is the function everyone uses:

  1. private function encodePassword($password) {
  2.     $password="\"".$password."\"";
  3.     $encoded = "";
  4.     for ($i = 0; $i < strlen($password); $i++) {
  5.         $encoded .= "{$password{$i}}\000";
  6.     }
  7.     return $encoded;
  8. }

Leave a Reply

Your email address will not be published. Required fields are marked *