Hello,

I have written a test script to do searches to AD with PHP. I have tested this script with PHP 4.3.X and PHP 5.0.0.

It works perfectly except it won't follow LDAP referrals. So if I try to search a user who has an account in separate subdomain than me, it won't find it... Example: I have an account (which I use to bind) in sd1.ad.domain.com and the user which I search have his/her account in sd2.ad.domain.com. When I search, I can found all accounts in sd1.ad.domain.com, but I can't get anything from sd2.ad.domain.com. However with COM objects I can find user from other domain also... But I don't want to use COM objects, becouse they won't work with Linux environment.

So anyone knows how to follow LDAP referrals with PHP? I quess that this is the problem why I can't search from other subdomains, becouse I have tried separate LDAP browser programs and they can seach and display information from other domains as well with the same user account...

If you find this code useful, please tell me! But remember that this is not very clean piece of code and it is only a quick hack to test user searching in AD.

Test code is in the next post, becouse it didn't fit on this one :-)

-Zipi

    <html>
    <head>
    <title>LDAP Test</title>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <style>
    BODY {
    	font-family: Verdana, Arial, Helvetica;
    	font-size: 12px;
    }
    
    P {
    	font-family: Verdana, Arial, Helvetica;
    	font-size: 12px;
    }
    </style>
    </head>
    <?
    //Functions
    //Parse UserAccountControl Flgs to more human understandable form...
    function parseUACF($uacf) {
    	//All flags array
    	$flags = array(	"TRUSTED_TO_AUTH_FOR_DELEGATION"=>	16777216,
    					"PASSWORD_EXPIRED"				=>	8388608,
    					"DONT_REQ_PREAUTH"				=>	4194304,
    					"USE_DES_KEY_ONLY"				=>	2097152,
    					"NOT_DELEGATED"					=>	1048576,
    					"TRUSTED_FOR_DELEGATION"		=>	524288,
    					"SMARTCARD_REQUIRED"			=>	262144,
    					"MNS_LOGON_ACCOUNT"				=>	131072,
    					"DONT_EXPIRE_PASSWORD"			=>	65536,
    					"SERVER_TRUST_ACCOUNT"			=>	8192,
    					"WORKSTATION_TRUST_ACCOUNT"		=>	4096,
    					"INTERDOMAIN_TRUST_ACCOUNT"		=>	2048,
    					"NORMAL_ACCOUNT"				=>	512,
    					"TEMP_DUPLICATE_ACCOUNT"		=>	256,
    					"ENCRYPTED_TEXT_PWD_ALLOWED"	=>	128,
    					"PASSWD_CANT_CHANGE"			=>	64,
    					"PASSWD_NOTREQD"				=>	32,
    					"LOCKOUT"						=>	16,
    					"HOMEDIR_REQUIRED"				=>	8,
    					"ACCOUNTDISABLE"				=>	2,
    					"SCRIPT" 						=> 	1);
    
    //Parse flags to text
    $retval = array();
    while (list($flag, $val) = each($flags)) {
    	if ($uacf >= $val) {
    		$uacf -= $val;
    		$retval[] = $flag;
    	}
    }
    
    //Return human friendly flags
    return($retval);
    }
    
    //parse SamAccountType value to text
    function parseSAT($samtype) {
    	$stypes = array(	805306368	=>	"NORMAL_ACCOUNT",
    						805306369	=>	"WORKSTATION_TRUST",
    						805306370	=>	"INTERDOMAIN_TRUST",
    						268435456	=>	"SECURITY_GLOBAL_GROUP",
    						268435457	=>	"DISTRIBUTION_GROUP",
    						536870912	=>	"SECURITY_LOCAL_GROUP",
    						536870913	=>	"DISTRIBUTION_LOCAL_GROUP");
    
    $retval = "";
    while (list($sat, $val) = each($stypes)) {
    	if ($samtype == $sat) {
    		$retval = $val;
    		break;
    	}
    }
    if (empty($retval)) $retval = "UNKNOWN_TYPE_" . $samtype;
    
    return($retval);
    }
    
    //Parse GroupType value to text
    function parseGT($grouptype) {
    	$gtypes = array(	-2147483643	=>	"SECURITY_BUILTIN_LOCAL_GROUP",
    						-2147483644	=>	"SECURITY_DOMAIN_LOCAL_GROUP",
    						-2147483646	=>	"SECURITY_GLOBAL_GROUP",
    						2			=>	"DISTRIBUTION_GLOBAL_GROUP",
    						4			=>	"DISTRIBUTION_DOMAIN_LOCAL_GROUP",
    						8			=>	"DISTRIBUTION_UNIVERSAL_GROUP");
    
    $retval = "";
    while (list($gt, $val) = each($gtypes)) {
    	if ($grouptype == $gt) {
    		$retval = $val;
    		break;
    	}
    }
    if (empty($retval)) $retval = "UNKNOWN_TYPE_" . $grouptype;
    
    return($retval);
    }
    
    function getObjectSid($adConn, $dn, $distname) {
    	//Select which attributes wa want
    	$attrs = array("objectsid");
    
    //Filter creation
    $filter = "distinguishedname=" . addslashes($distname);
    
    //Do the seacrh!
    $search = ldap_search($adConn, $dn, $filter, $attrs) or die("**** happens, no connection!");
    
    $entry = ldap_first_entry($adConn, $search);
    $vals = ldap_get_values_len($adConn, $entry, "objectsid");
    return(bin2hex($vals[0]));
    }
    if (empty($_GET["dn"])) $_GET["dn"] = "DC=sd1,DC=ad,DC=domain,DC=com"; 
    if (empty($_GET["host"])) $_GET["host"] = "ldap://dc.sd1.ad.domain.com"; 
    if (empty($_GET["user"])) $_GET["user"] = "username@sd1.ad.domain.com"; 
    ?>
    <body>
    <h3>AD Search with plain LDAP queries</h3>
    <p>
      <form method="GET">
        <b>Search Criteria:</b><br>
        <select name="filter">
            <option value="cn"<? if ($_GET["filter"]=="cn") echo " selected"; ?>>cn</option>
            <option value="sn"<? if ($_GET["filter"]=="sn") echo " selected"; ?>>sn</option>
            <option value="userprincipalname"<? if ($_GET["filter"]=="userprincipalname") echo " selected"; ?>>userprincipalname</option>
            <option value="samaccountname"<? if ($_GET["filter"]=="samaccountname" or empty($_GET["filter"])) echo " selected"; ?>>samaccountname</option>
            <option value="telephonenumber"<? if ($_GET["filter"]=="telephonenumber") echo " selected"; ?>>telephonenumber</option>
            <option value="l"<? if ($_GET["filter"]=="l") echo " selected"; ?>>City</option>
        </select>
    	=
        <input type="text" name="keyword" size="30" maxlength="100" value="<? echo $_GET["keyword"]; ?>" /><br>
    	<input type="checkbox" name="debugarray" id="debugarray" value="1"<? if ($_GET["debugarray"] == 1) echo " checked"; ?>><label for="debugarray">View debug array</label><br>
    	<input type="checkbox" name="test" id="test" value="1"<? if ($_GET["test"] == 1) echo " checked"; ?>><label for="test">Get results also with COM objects, it uses different ways than just an ldap, which is not good...</label><br>
    	<br>
        DN = <input type="text" name="dn" size="50" maxlength="100" value="<? echo $_GET["dn"]; ?>" /><br>
        host = <input type="text" name="host" size="50" maxlength="100" value="<? echo $_GET["host"]; ?>" /><br>
        user/password = <input type="text" name="user" size="35" maxlength="100" value="<? echo $_GET["user"]; ?>" /> <input type="password" name="pass" size="10" maxlength="100" value="" /> If empty, defaults used.<br>
    	<br>
        <br><input type="submit" value="Do the thing!">
      </form>
    </p>
    
    <?php
    //Connection parameters
    $host = $_GET["host"];
    if (empty($_GET["user"]) or empty($_GET["pass"])) {
      $user = "default.user@sd1.ad.domain.com";
      $pass = "default.password";
    } else {
      $user = $_GET["user"];
      $pass = $_GET["pass"];
    }
    
    //Just a test with COM objects... Not very nice though!
    if ($_GET["test"] == 1) {
    $objConn = new COM("ADODB.Connection");
    $objConn->Provider = "ADsDSOObject";
    $objConn->Open("ADs Provider", $user, $pass);
    
    $objRs = new COM("ADODB.Recordset");
    $objCom = new COM("ADODB.Command");
    
    $objCom->ActiveConnection = $objConn;
    $objCom->CommandText = "SELECT objectsid,samAccountname,userAccountControl,userPrincipalName,ADsPath,givenName,sn,telephoneNumber,mail,displayName, department, distinguishedName FROM 'GC://dc=corp,dc=storaenso,dc=com' WHERE '" . $_GET["filter"] . "' = '" . $_GET["keyword"] . "'";
    
    $objRs = $objCom->Execute; 
    
    echo "<p><b>COM object results:</b> " . $objRs->RecordCount . "</p>";
    
    while (!$objRs->EOF) {
    	echo "<p>";
    	for ($i=0; $i < $objRs->Fields->Count; $i++) {
    		echo "$i: " . $objRs->Fields[$i]->value . "<br>\n";
    	}
    	$test = $objRs->Fields[11]->value;
        echo "<b>ObjectSid:</b> " . $test . " (DEC?)";
    	echo "</p>";
    $objRs->MoveNext();
    }
    
    } //end of test
    
    //The real thing with PHP
    if (!empty($_GET["keyword"]) and !empty($_GET["dn"])) {
    	//Connect to the AD
    	$adConn = ldap_connect($host, 636) or die("Could not connect!");
    
    //Set protocol verison
    ldap_set_option($adConn, LDAP_OPT_PROTOCOL_VERSION, 3) or die ("Could not set ldap protocol1");
    
    //Set referrals... Won't work without this...
    ldap_set_option($adConn, LDAP_OPT_REFERRALS, 0) or die ("Could not set ldap protocol2");
    
    //Bind the user
    $bd = ldap_bind($adConn, $user, $pass) or die ("Could not bind");
    
    //Create DN
    $dn = $_GET["dn"];
    
    //Create the filter
    $filter = $_GET['filter'] . "=" . $_GET["keyword"];
    
    //Do the search
    $search = ldap_search($adConn, $dn, utf8_encode($filter));
    
    //Get results to an array
    $entries = ldap_get_entries($adConn, $search);
    
    //Free willy! Err... Result :-)
    ldap_free_result($search);
    
    //End binding
    //ldap_unbind($adConn);
    
    } else {
    	$entries = array();
    }
    
    echo "<p><b>Search filter:</b> " . $filter . "<br>";
    echo "<b>Number of entries:</b> " . $entries["count"] . "</p>\n";
    
    if ($entries["count"] > 0) {
    
    //Print entries
    for ($i = 0; $i < $entries["count"] ; $i++) {
    	echo "<hr>\n";
    	echo "<p><b>Entry:</b> " . ($i+1) . "<br>\n";
    	$SamAccountType = parseSAT(utf8_decode($entries[$i]["samaccounttype"][0]));
    	echo "<b>Sam-Account-Type:</b> " . $SamAccountType . "<br>\n";
    	if (ereg("GROUP", $SamAccountType)) echo "<b>Group-Type:</b> " . parseGT(utf8_decode($entries[$i]["grouptype"][0])) . "<br>\n";
    	echo implode("<br>\n", parseUACF(utf8_decode($entries[$i]["useraccountcontrol"][0]))) . "<br>\n";
    	if (utf8_decode($entries[$i]["pwdlastset"][0]) == 0 and utf8_decode($entries[$i]["pwdlastset"][0]) != "") echo "<b>User must change password at next logon</b><br>\n";
    	echo "<b>ObjectSid:</b> " . bin2hex($entries[$i]["objectsid"][0]) . " <font size=\"1\">&lt;-Buggy method (HEX)</font><br>\n";
    	$objectsid = getObjectSid($adConn, $dn, $entries[$i]["distinguishedname"][0]);
    	echo "<b>ObjectSid:</b> " . $objectsid . " <font size=\"1\">&lt;-Less buggy? (HEX) At least it seems to work and the data is somehow sensible.</font><br>\n";
    	echo "<b>ObjectSid lenght:</b> " . strlen($objectsid) . "<br>\n";
    	//echo "<b>ObjectSid:</b> " . getObjectSid($adConn, $entries[$i]["objectcategory"][0], $entries[$i]["distinguishedname"][0]) . "<br>\n";
    	echo "</p>";
    
    // $ii = attributes for entry
    // $iii = values per attribute
    for ($ii=0; $ii<$entries[$i]["count"]; $ii++){
    	$data = $entries[$i][$ii];
    	for ($iii=0; $iii<$entries[$i][$data]["count"]; $iii++) {
    		echo "<b>" . utf8_decode($data) . ":</b>&nbsp;&nbsp;" . utf8_decode($entries[$i][$data][$iii]) . "<br>\n";
    	}
    }
    
    if ($_GET["debugarray"] == 1) {
    	echo "<pre>";
    	print_r($entries[$i]);
    	echo "</pre>";
    }
    }
    
    } else {
       echo "<p>No results found!</p>";
    }
    
    ?>
    
    
    </body>
    </html>

      I think I'll have to study ldap_first_reference and ldap_next_reference functions...

      However I cannot find any examples, even trough I have googled myself almost to insanity :-) Does anybody know how to use these functions?

        Write a Reply...