, so if I wanted to scroll to * dc=example,dc=com for server 3, the URL would be: * tree.php#3_dc%3Dexample%2Cdc%3Dcom * * @package lists * @subpackage tree * @author David Smith * @author Roland Gruber */ /* * Strip slashes from GET and POST variables if this * PHP install is configured to automatically addslashes() */ if (get_magic_quotes_gpc() && (! isset($slashes_stripped) || ! $slashes_stripped)) { array_stripslashes($_GET); array_stripslashes($_POST); $slashes_stripped = true; } /** * Strips all slashes from the specified array in place (pass by ref). * @param Array $array The array to strip slashes from, typically one of * $_GET, $_POST, or $_COOKIE. */ function array_stripslashes(&$array) { if (is_array($array)) while (list($key) = each($array)) if (is_array($array[$key]) && $key != $array) array_stripslashes($array[$key]); else $array[$key] = stripslashes($array[$key]); } /** * Prints the HTML of the tree view. */ function draw_server_tree() { global $tree; global $tree_icons; $refresh_href = 'refresh.php'; $create_href = 'create_form.php?container=' . rawurlencode( $_SESSION['config']->get_Suffix('tree') ); // Draw the quick-links below the server name: // ( schema | search | refresh | create ) echo '

 *   Array 
 *    ( 
 *      [0] => Array 
 *          (
 *             [dc=example,dc=com] => "dcobject.png"
 *          )
 *      [1] => Array 
            (
 *            [o=Corporation] => "o.png"
 *          )
 *     )
 * 
 * This function is not meant as a user-callable function, but rather a convenient, automated method for 
 * setting up the initial data structure for the tree viewer's icon cache.
 */
function build_initial_tree_icons()
{
	$tree_icons = array();
	// initialize an empty array for each server
	$tree_icons = array();
	$tree_icons[ $_SESSION['config']->get_Suffix('tree') ] = get_icon( $_SESSION['config']->get_Suffix('tree') );
	return $tree_icons;
}
/**
 * Gets whether an entry exists based on its DN. If the entry exists, 
 * returns true. Otherwise returns false.
 *
 * @param string $dn The DN of the entry of interest.
 *
 * @return bool
 */
function dn_exists( $dn )
{
	$search_result = @ldap_read( $_SESSION['ldap']->server(), $dn, 'objectClass=*', array('dn') );
	if( ! $search_result )
		return false;
	$num_entries = ldap_count_entries( $_SESSION['ldap']->server(), $search_result );
	if( $num_entries > 0 )
		return true;
	else
		return false;
}
/**
 * Gets a list of child entries for an entry. Given a DN, this function fetches the list of DNs of
 * child entries one level beneath the parent. For example, for the following tree:
 *
 * 
 * dc=example,dc=com
 *   ou=People
 *      cn=Dave
 *      cn=Fred
 *      cn=Joe
 *      ou=More People
 *         cn=Mark
 *         cn=Bob
 * 
 *
 * Calling get_container_contents( "ou=people,dc=example,dc=com" )
 * would return the following list:
 * 
 * 
 *  cn=Dave
 *  cn=Fred
 *  cn=Joe
 *  ou=More People
 * 
 * 
 * @param string $dn The DN of the entry whose children to return.
 * @param int $size_limit (optional) The maximum number of entries to return. 
 *             If unspecified, no limit is applied to the number of entries in the returned.
 * @param string $filter (optional) An LDAP filter to apply when fetching children, example: "(objectClass=inetOrgPerson)"
 * @return array An array of DN strings listing the immediate children of the specified entry.
 */
function get_container_contents( $dn, $size_limit=0, $filter='(objectClass=*)' )
{
	$search = @ldap_list( $_SESSION['ldap']->server(), $dn, $filter, array( 'dn' ), 1, $size_limit, 0);
	if( ! $search )
		return array();
	$search = ldap_get_entries( $_SESSION['ldap']->server(), $search );
	$return = array();
	for( $i=0; $i<$search['count']; $i++ ) {
		$entry = $search[$i];
		$dn = $entry['dn'];
		$return[] = $dn;
	}
	return $return;
}
/**
 * Given a DN and server ID, this function reads the DN's objectClasses and
 * determines which icon best represents the entry. The results of this query
 * are cached in a session variable so it is not run every time the tree
 * browser changes, just when exposing new DNs that were not displayed
 * previously. That means we can afford a little bit of inefficiency here
 * in favor of coolness. :)
 *
 * This function returns a string like "country.png". All icon files are assumed
 * to be contained in the /../../graphics/ directory of phpLDAPadmin.
 *
 * Developers are encouraged to add new icons to the images directory and modify
 * this function as needed to suit their types of LDAP entries. If the modifications
 * are general to an LDAP audience, the phpLDAPadmin team will gladly accept them
 * as a patch.
 * 
 * @param string $dn The DN of the entry whose icon you wish to fetch.
 *
 * @return string
 */
function get_icon( $dn )
{
	// fetch and lowercase all the objectClasses in an array
	$object_classes = get_object_attr( $dn, 'objectClass', true );
	if( $object_classes === null || $object_classes === false || ! is_array( $object_classes ) )
		$object_classes = array();
	foreach( $object_classes as $i => $class )
		$object_classes[$i] = strtolower( $class );
	$rdn = get_rdn( $dn );
    $rdn_parts = explode( '=', $rdn, 2 );
    $rdn_value = isset( $rdn_parts[0] ) ? $rdn_parts[0] : null;
    unset( $rdn_parts );
	// return icon filename based upon objectClass value
	if( in_array( 'sambaaccount', $object_classes ) &&
		'$' == $rdn{ strlen($rdn) - 1 } )
		return 'nt_machine.png';
	if( in_array( 'sambaaccount', $object_classes ) )
		return 'nt_user.png';
	elseif( in_array( 'sambadomain', $object_classes ) )
		return 'smbDomain.png';
	elseif( in_array( 'person', $object_classes ) ||
	    in_array( 'organizationalperson', $object_classes ) ||
	    in_array( 'inetorgperson', $object_classes ) ||
	    in_array( 'account', $object_classes ) ||
   	    in_array( 'posixaccount', $object_classes )  )
		return 'user.png';
	elseif( in_array( 'organization', $object_classes ) )
		return 'o.png';
	elseif( in_array( 'organizationalunit', $object_classes ) )
		return 'ou.png';
	elseif( in_array( 'organizationalrole', $object_classes ) )
		return 'uid.png';
	elseif( in_array( 'dcobject', $object_classes ) ||
		in_array( 'domainrelatedobject', $object_classes ) ||
		in_array( 'domain', $object_classes ) ||
        in_array( 'builtindomain', $object_classes )) 
		return 'dc.png';
    elseif( in_array( 'alias', $object_classes ) )
        return 'go.png';
    elseif( in_array( 'room', $object_classes ) )
        return 'door.png';
    elseif( in_array( 'device', $object_classes ) )
        return 'device.png';
    elseif( in_array( 'document', $object_classes ) )
        return 'document.png';
	elseif( in_array( 'jammvirtualdomain', $object_classes ) )
		return 'mail.png';
	elseif( in_array( 'locality', $object_classes ) )
		return 'locality.png';
	elseif( in_array( 'posixgroup', $object_classes ) ||
		in_array( 'groupofnames', $object_classes ) ||
		in_array( 'group', $object_classes ) )
		return 'ou.png';
	elseif( in_array( 'applicationprocess', $object_classes ) )
		return 'process.png';
	elseif( in_array( 'groupofuniquenames', $object_classes ) )
		return 'uniquegroup.png';
	elseif( in_array( 'iphost', $object_classes ) )
		return 'host.png';
	elseif( in_array( 'nlsproductcontainer', $object_classes ) )
        return 'n.png';
	elseif( in_array( 'ndspkikeymaterial', $object_classes ) )
        return 'lock.png';
	elseif( in_array( 'server', $object_classes ) )
        return 'server-small.png';
	elseif( in_array( 'volume', $object_classes ) )
        return 'hard-drive.png';
	elseif( in_array( 'ndscatcatalog', $object_classes ) )
        return 'catalog.png';
	elseif( in_array( 'resource', $object_classes ) )
        return 'n.png';
	elseif( in_array( 'ldapgroup', $object_classes ) )
        return 'ldap-server.png';
	elseif( in_array( 'ldapserver', $object_classes ) )
        return 'ldap-server.png';
	elseif( in_array( 'nisserver', $object_classes ) )
        return 'ldap-server.png';
	elseif( in_array( 'rbscollection', $object_classes ) )
        return 'ou.png';
	elseif( in_array( 'dfsconfiguration', $object_classes ) )
        return 'nt_machine.png';
	elseif( in_array( 'applicationsettings', $object_classes ) )
        return 'server-settings.png';
	elseif( in_array( 'aspenalias', $object_classes ) )
        return 'mail.png';
	elseif( in_array( 'container', $object_classes ) )
        return 'folder.png';
	elseif( in_array( 'ipnetwork', $object_classes ) )
        return 'network.png';
	elseif( in_array( 'samserver', $object_classes ) )
        return 'server-small.png';
	elseif( in_array( 'lostandfound', $object_classes ) )
        return 'find.png';
	elseif( in_array( 'infrastructureupdate', $object_classes ) )
        return 'server-small.png';
	elseif( in_array( 'filelinktracking', $object_classes ) )
        return 'files.png';
	elseif( in_array( 'automountmap', $object_classes ) ||
            in_array( 'automount', $object_classes ) )
        return 'hard-drive.png';
    elseif( 0 === strpos( $rdn_value, "ipsec" ) || 
            0 == strcasecmp( $rdn_value, "IP Security" ) ||
            0 == strcasecmp( $rdn_value, "MSRADIUSPRIVKEY Secret" ) ||
            0 === strpos( $rdn_value, "BCKUPKEY_" ) )
        return 'lock.png';
	elseif( 0 == strcasecmp( $rdn_value, "MicrosoftDNS" ) )
        return 'dc.png';
	// Oh well, I don't know what it is. Use a generic icon.
	else
		return 'object.png';
}
/**
 * Much like get_object_attrs(), but only returns the values for
 * one attribute of an object. Example calls:
 *
 * 
 * print_r( get_object_attr( 0, "cn=Bob,ou=people,dc=example,dc=com", "sn" ) );
 * // prints:
 * //  Array 
 * //    ( 
 * //       [0] => "Smith"
 * //    )
 *
 * print_r( get_object_attr( 0, "cn=Bob,ou=people,dc=example,dc=com", "objectClass" ) );
 * // prints:
 * //  Array 
 * //    ( 
 * //       [0] => "top"
 * //       [1] => "person"
 * //    )
 * 
 * 
 * @param string $dn The distinguished name (DN) of the entry whose attributes/values to fetch.
 * @param string $attr The attribute whose value(s) to return (ie, "objectClass", "cn", "userPassword")
 * @param bool $lower_case_attr_names (optional) If true, all keys of the returned associative
 *              array will be lower case. Otherwise, they will be cased as the LDAP server returns
 *              them.
 * @see get_object_attrs
 */
function get_object_attr( $dn, $attr )
{
	$search = @ldap_read( $_SESSION['ldap']->server(), $dn, '(objectClass=*)', array( $attr ), 0, 0, 0 );
	if( ! $search )
		return false;
	$entry = ldap_first_entry( $_SESSION['ldap']->server(), $search );
	if( ! $entry )
		return false;
	
	$attrs = ldap_get_attributes( $_SESSION['ldap']->server(), $entry );
	if( ! $attrs || $attrs['count'] == 0 )
		return false;
	$vals = ldap_get_values( $_SESSION['ldap']->server(), $entry, $attr );
	unset( $vals['count'] );
	return $vals;
}
/**
 * Given a DN string, this returns the 'RDN' portion of the string.
 * For example. given 'cn=Manager,dc=example,dc=com', this function returns
 * 'cn=Manager' (it is really the exact opposite of get_container()).
 *
 * @param string $dn The DN whose RDN to return.
 * @param bool $include_attrs If true, include attributes in the RDN string. 
 *               See http://php.net/ldap_explode_dn for details
 *
 * @return string The RDN
 * @see get_container
 */
function get_rdn( $dn, $include_attrs=0 )
{
	if( $dn == null )
		return null;
	$rdn = pla_explode_dn( $dn, $include_attrs );
	if( 0 == count($rdn) )
		return $dn;
	if( ! isset( $rdn[0] ) )
		return $dn;
	$rdn = $rdn[0];
	return $rdn;
}
/**
 * Explode a DN into an array of its RDN parts. This function is UTF-8 safe
 * and replaces the buggy PHP ldap_explode_dn() which does not properly
 * handle UTF-8 DNs and also causes segmentation faults with some inputs.
 *
 * @param string $dn The DN to explode.
 * @param int $with_attriutes (optional) Whether to include attribute names (see http://php.net/ldap_explode_dn for details)
 *
 * @return array An array of RDN parts of this format:
 * 
 *   Array
 *    (
 *       [0] => uid=ppratt
 *       [1] => ou=People
 *       [2] => dc=example
 *       [3] => dc=com
 *    )
 * 
 */
function pla_explode_dn( $dn, $with_attributes=0 )
{
  // replace "\," with the hexadecimal value for safe split
  $var = preg_replace("/\\\,/","\\\\\\\\2C",$dn);
  // split the dn
  $result = explode(",",$var);
  
  //translate hex code into ascii for display
  foreach( $result as $key => $value )
    $result[$key] = preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $value);
  
  return $result;
}
/** 
 * Returns an HTML-beautified version of a DN.
 * Internally, this function makes use of pla_explode_dn() to break the 
 * the DN into its components. It then glues them back together with
 * "pretty" HTML. The returned HTML is NOT to be used as a real DN, but 
 * simply displayed.
 * 
 * @param string $dn The DN to pretty-print.
 * @return string
 */
function pretty_print_dn( $dn )
{
	$dn = pla_explode_dn( $dn );
	foreach( $dn as $i => $element ) {
		$element = htmlspecialchars( $element );
		$element = explode( '=', $element, 2 );
		$element = implode( '=', $element );
		$dn[$i] = $element;
	}
	$dn = implode( ',', $dn );
	return $dn;
}
/**
 * Compares 2 DNs. If they are equivelant, returns 0, otherwise,
 * returns their sorting order (similar to strcmp()):
 *      Returns < 0 if dn1 is less than dn2.
 *      Returns > 0 if dn1 is greater than dn2.
 *
 * The comparison is performed starting with the top-most element 
 * of the DN. Thus, the following list:
 *    
 *       ou=people,dc=example,dc=com
 *       cn=Admin,ou=People,dc=example,dc=com
 *       cn=Joe,ou=people,dc=example,dc=com
 *       dc=example,dc=com
 *       cn=Fred,ou=people,dc=example,dc=org
 *       cn=Dave,ou=people,dc=example,dc=org
 *    
 * Will be sorted thus using usort( $list, "pla_compare_dns" ):
 *    
 *       dc=com
 *       dc=example,dc=com
 *       ou=people,dc=example,dc=com
 *       cn=Admin,ou=People,dc=example,dc=com
 *       cn=Joe,ou=people,dc=example,dc=com
 *       cn=Dave,ou=people,dc=example,dc=org
 *       cn=Fred,ou=people,dc=example,dc=org
 *    
 *
 * @param string $dn1 The first of two DNs to compare
 * @param string $dn2 The second of two DNs to compare
 * @return int
 */
function pla_compare_dns( $dn1, $dn2 )
{
	// If they are obviously the same, return immediately
	if( 0 === strcasecmp( $dn1, $dn2 ) )
		return 0;
	
	$dn1_parts = pla_explode_dn( pla_reverse_dn($dn1) );
	$dn2_parts = pla_explode_dn( pla_reverse_dn($dn2) );
	assert( is_array( $dn1_parts ) );
	assert( is_array( $dn2_parts ) );
	
	// Foreach of the "parts" of the smaller DN
	for( $i=0; $i
 *   cn=Brigham,ou=People,dc=example,dc=com
 * 
 * Becomes: 
 * 
 *   dc=com,dc=example,ou=People,cn=Brigham
 * 
 * This makes it possible to sort lists of DNs such that they are grouped by container.
 *
 * @param string $dn The DN to reverse
 *
 * @return string The reversed DN
 *
 * @see pla_compare_dns
 */
function pla_reverse_dn($dn)
{
	foreach (pla_explode_dn($dn) as $key => $branch) {
		// pla_expode_dn returns the array with an extra count attribute, we can ignore that.
		if ( $key === "count" ) continue;
		if (isset($rev)) {
			$rev = $branch.",".$rev;
		} else {
			$rev = $branch;
		}
	}
	return $rev;
}
/**
 * Gets a DN string using the user-configured tree_display_format string to format it.
 */
function draw_formatted_dn( $dn )
{
    $format = '%rdn';
    preg_match_all( "/%[a-zA-Z_0-9]+/", $format, $tokens );
    $tokens = $tokens[0];
    foreach( $tokens as $token ) {
        if( 0 == strcasecmp( $token, '%dn' ) )
            $format = str_replace( $token, pretty_print_dn( $dn ), $format );
        elseif( 0 == strcasecmp( $token, '%rdn' ) )
            $format = str_replace( $token, pretty_print_dn( get_rdn( $dn ) ), $format );
        elseif( 0 == strcasecmp( $token, '%rdnvalue' ) ) {
            $rdn = get_rdn( $dn );
            $rdn_value = explode( '=', $rdn, 2 );
            $rdn_value = $rdn_value[1];
            $format = str_replace( $token, $rdn_value, $format );
        } else {
            $attr_name = str_replace( '%', '', $token );
            $attr_values = get_object_attr( $dn, $attr_name );
            if( null == $attr_values )
                $display = 'none';
            elseif( is_array( $attr_values ) )
                $display = htmlspecialchars( implode( ', ',  $attr_values ) );
            else
                $display = htmlspecialchars( $attr_values );
            $format = str_replace( $token, $display, $format );
        }
    }
    echo $format;
}
/**
 * Gets the attributes/values of an entry. Returns an associative array whose
 * keys are attribute value names and whose values are arrays of values for
 * said attribute. Optionally, callers may specify true for the parameter 
 * $lower_case_attr_names to force all keys in the associate array (attribute 
 * names) to be lower case. 
 * 
 * Sample return value of get_object_attrs( 0, "cn=Bob,ou=pepole,dc=example,dc=com" )
 *
 * 
 * Array
 *  (
 *   [objectClass] => Array
 *       (
 *           [0] => person
 *           [1] => top
 *       )
 *   [cn] => Array
 *       (
 *           [0] => Bob
 *       )
 *   [sn] => Array
 *       (
 *           [0] => Jones
 *       )
 *   [dn] => Array
 *       (
 *            [0] => cn=Bob,ou=pepole,dc=example,dc=com
 *       )
 *  )
 * 
 *
 * @param string $dn The distinguished name (DN) of the entry whose attributes/values to fetch.
 * @param bool $lower_case_attr_names (optional) If true, all keys of the returned associative
 *              array will be lower case. Otherwise, they will be cased as the LDAP server returns
 *              them.
 * @param int $deref For aliases and referrals, this parameter specifies whether to 
 *            follow references to the referenced DN or to fetch the attributes for
 *            the referencing DN. See http://php.net/ldap_search for the 4 valid
 *            options.
 * @return array
 * @see get_entry_system_attrs
 * @see get_object_attr
 */
function get_object_attrs( $dn, $lower_case_attr_names=false, $deref=LDAP_DEREF_NEVER )
{
	$conn = $_SESSION['ldap']->server();
	$search = @ldap_read( $conn, $dn, '(objectClass=*)', array( ), 0, 0, 0, $deref );
	if( ! $search )
		return false;
	$entry = ldap_first_entry( $conn, $search );
	if( ! $entry )
		return false;
	
	$attrs = ldap_get_attributes( $conn, $entry );
	if( ! $attrs || $attrs['count'] == 0 )
		return false;
	$num_attrs = $attrs['count'];
	unset( $attrs['count'] );
	// strip numerical inices
	for( $i=0; $i<$num_attrs; $i++ )
		unset( $attrs[$i] );
	$return_array = array();
	foreach( $attrs as $attr => $vals ) {
		if( $lower_case_attr_names )
			$attr = strtolower( $attr );
		if( is_attr_binary( $attr ) )
			$vals = ldap_get_values_len( $conn, $entry, $attr );
		unset( $vals['count'] );
		$return_array[ $attr ] = $vals;
	}
	ksort( $return_array );
	return $return_array;
}
/**
 * Given an attribute name and server ID number, this function returns
 * whether the attrbiute may contain binary data. This is useful for 
 * developers who wish to display the contents of an arbitrary attribute
 * but don't want to dump binary data on the page.
 * 
 * @param string $attr_name The name of the attribute to test.
 * @return bool
 *
 * @see is_jpeg_photo
 */
function is_attr_binary( $attr_name )
{
    $attr_name = strtolower( $attr_name );
    /** Determining if an attribute is binary can be an expensive
       operation. We cache the results for each attr name on each
       server in the $attr_cache to speed up subsequent calls. 
       The $attr_cache looks like this:
       Array 
        0 => Array 
              'objectclass' => false
              'cn' => false
              'usercertificate' => true
        1 => Array 
              'jpegphoto' => true 
              'cn' => false
    */
    static $attr_cache;
    if( isset( $attr_cache[ $attr_name ] ) )
        return $attr_cache[ $attr_name ];
    if( $attr_name == 'userpassword' ) {
        $attr_cache[ $attr_name ] = false;
        return false;
    }
    // Quick check: If the attr name ends in ";binary", then it's binary.
	if( 0 == strcasecmp( substr( $attr_name, strlen( $attr_name ) - 7 ), ";binary" ) ) {
        $attr_cache[ $attr_name ] = true;
		return true;
    }
    // See what the server schema says about this attribute
	$schema_attr = get_schema_attribute( $attr_name );
	if( ! $schema_attr ) {
        // Strangely, some attributeTypes may not show up in the server
        // schema. This behavior has been observed in MS Active Directory.
        $type = null;
        $syntax = null;
    } else {
        $type = $schema_attr->getType();
        $syntax = $schema_attr->getSyntaxOID();
    }
	if(	0 == strcasecmp( $type, 'Certificate' ) ||
		0 == strcasecmp( $type, 'Binary' ) ||
		0 == strcasecmp( $attr_name, 'usercertificate' ) ||
		0 == strcasecmp( $attr_name, 'usersmimecertificate' ) ||
		0 == strcasecmp( $attr_name, 'networkaddress' ) ||
		0 == strcasecmp( $attr_name, 'objectGUID' ) ||
		0 == strcasecmp( $attr_name, 'objectSID' ) ||
		$syntax == '1.3.6.1.4.1.1466.115.121.1.10' ||
		$syntax == '1.3.6.1.4.1.1466.115.121.1.28' ||
		$syntax == '1.3.6.1.4.1.1466.115.121.1.5' ||
		$syntax == '1.3.6.1.4.1.1466.115.121.1.8' ||
		$syntax == '1.3.6.1.4.1.1466.115.121.1.9' ) {
            $attr_cache[ $attr_name ] = true;
			return true;
    } else {
            $attr_cache[ $attr_name ] = false;
			return false;
    }
}
/** 
 * Prunes off anything after the ";" in an attr name. This is useful for
 * attributes that may have ";binary" appended to their names. With 
 * real_attr_name(), you can more easily fetch these attributes' schema
 * with their "real" attribute name.
 *
 * @param string $attr_name The name of the attribute to examine.
 * @return string
 */
function real_attr_name( $attr_name )
{
	$attr_name = preg_replace( "/;.*$/U", "", $attr_name );
	return $attr_name;
}
/** 
 * Gets the operational attributes for an entry. Given a DN, this function fetches that entry's
 * operational (ie, system or internal) attributes. These attributes include "createTimeStamp", 
 * "creatorsName", and any other attribute that the LDAP server sets automatically. The returned
 * associative array is of this form:
 * 
 *  Array 
 *  (
 *    [creatorsName] => Array 
 *        (
 *           [0] => "cn=Admin,dc=example,dc=com"
 *        )
 *    [createTimeStamp]=> Array 
 *        (
 *           [0] => "10401040130"
 *        )
 *    [hasSubordinates] => Array 
 *        (
 *           [0] => "FALSE"
 *        )
 *  )
 * 
 *
 * @param string $dn The DN of the entry whose interal attributes are desired.
 * @param int $deref For aliases and referrals, this parameter specifies whether to 
 *            follow references to the referenced DN or to fetch the attributes for
 *            the referencing DN. See http://php.net/ldap_search for the 4 valid
 *            options.
 * @return array An associative array whose keys are attribute names and whose values
 *              are arrays of values for the aforementioned attribute.
 */
function get_entry_system_attrs( $dn, $deref=LDAP_DEREF_NEVER )
{
	$conn = $_SESSION['ldap']->server();
	$attrs = array( 'creatorsname', 'createtimestamp', 'modifiersname', 
			'structuralObjectClass', 'entryUUID',  'modifytimestamp', 
			'subschemaSubentry', 'hasSubordinates', '+' );
	$search = @ldap_read( $conn, $dn, '(objectClass=*)', $attrs, 0, 0, 0, $deref );
	if( ! $search )
		return false;
	$entry = ldap_first_entry( $conn, $search );
	if( ! $entry)
	    return false;
	$attrs = ldap_get_attributes( $conn, $entry );
	if( ! $attrs )
		return false;
	if( ! isset( $attrs['count'] ) )
		return false;
	$count = $attrs['count'];
	unset( $attrs['count'] );
	$return_attrs = array();
	for( $i=0; $i<$count; $i++ ) {
		$attr_name = $attrs[$i];
		unset( $attrs[$attr_name]['count'] );
		$return_attrs[$attr_name] = $attrs[$attr_name];
	}
	return $return_attrs;
}
function arrayLower($array) {
	foreach ($array as $key => $value) {
		$newarray[$key] = strtolower($value);
	}
	return $newarray;
}
/**
 * Used to determine if the specified attribute is indeed a jpegPhoto. If the
 * specified attribute is one that houses jpeg data, true is returned. Otherwise
 * this function returns false.
 *
 * @param string $attr_name The name of the attribute to test.
 * @return bool
 * @see draw_jpeg_photos
 */
function is_jpeg_photo( $attr_name )
{
	// easy quick check
	if( 0 == strcasecmp( $attr_name, 'jpegPhoto' ) ||
	    0 == strcasecmp( $attr_name, 'photo' ) )
	    return true;
	// go to the schema and get the Syntax OID
	$schema_attr = get_schema_attribute( $attr_name );
	if( ! $schema_attr )
		return false;
	$oid = $schema_attr->getSyntaxOID();
	$type = $schema_attr->getType();
	if( 0 == strcasecmp( $type, 'JPEG' ) )
		return true;
	if( $oid == '1.3.6.1.4.1.1466.115.121.1.28' )
		return true;
	return false;
}
/**
 * Given an attribute name and server ID number, this function returns
 * whether the attrbiute contains boolean data. This is useful for 
 * developers who wish to display the contents of a boolean attribute
 * with a drop-down.
 * 
 * @param string $attr_name The name of the attribute to test.
 * @return bool
 */
function is_attr_boolean( $attr_name )
{
    $type = ( $schema_attr = get_schema_attribute( $attr_name ) ) ? 
        $schema_attr->getType() : 
        null;
    if( 0 == strcasecmp( 'boolean', $type ) ||
        0 == strcasecmp( 'isCriticalSystemObject', $attr_name ) ||
        0 == strcasecmp( 'showInAdvancedViewOnly', $attr_name ) )
        return true;
    else
        return false;
}
/** 
 * Get whether a string looks like an email address (user@example.com).
 * 
 * @param string $str The string to analyze.
 * @return bool Returns true if the specified string looks like 
 *   an email address or false otherwise.
 */
function is_mail_string( $str )
{
    $mail_regex = "/^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*$/";
    if( preg_match( $mail_regex, $str ) )
        return true;
    else
        return false;
}
/** 
 * Get whether a string looks like a web URL (http://www.example.com/)
 * 
 * @param string $str The string to analyze.
 * @return bool Returns true if the specified string looks like 
 *   a web URL or false otherwise.
 */
function is_url_string( $str )
{
    $url_regex = '/(ftp|https?):\/\/+[\w\.\-\/\?\=\&]*\w+/';
    if( preg_match( $url_regex, $str ) )
        return true;
    else
        return false;
}
function sortAttrs($a,$b) {
	return strcmp ($a, $b);
}
/**
 * Determines if an attribute's value can contain multiple lines. Attributes that fall
 * in this multi-line category may be configured in config.php. Hence, this function
 * accesses the global variable $multi_line_attributes;
 *
 * Usage example:
 * 
 *  if( is_muli_line_attr( "postalAddress" ) )
 *      echo "";
 *  else 
 *      echo "";
 * 
 *
 * @param string $attr_name The name of the attribute of interestd (case insensivite)
 * @param string $val (optional) The current value of the attribute (speeds up the 
 *               process by searching for carriage returns already in the attribute value)
 * @return bool
 */
function is_multi_line_attr( $attr_name, $val=null )
{
    // First, check the optional val param for a \n or a \r
    if( null != $val && 
        ( false !== strpos( $val, "\n" ) || 
          false !== strpos( $val, "\r" ) ) )
        return true;
    // Next, compare strictly by name first
    global $multi_line_attributes;
    if( isset( $multi_line_attributes ) && is_array( $multi_line_attributes ) )
        foreach( $multi_line_attributes as $multi_line_attr_name )
            if( 0 == strcasecmp( $multi_line_attr_name, $attr_name ) ) {
                return true;
            }
        global $multi_line_syntax_oids;
        if( isset( $multi_line_syntax_oids ) && is_array( $multi_line_syntax_oids ) ) {
            $schema_attr = get_schema_attribute( $attr_name );
            if( ! $schema_attr )
                return false;
            $syntax_oid = $schema_attr->getSyntaxOID();
            if( ! $syntax_oid )
                return false;
            foreach( $multi_line_syntax_oids as $multi_line_syntax_oid )
                if( $multi_line_syntax_oid == $syntax_oid )
                    return true;
        }
    return false;
}
/**
 * Returns true if the attribute specified is required to take as input a DN.
 * Some examples include 'distinguishedName', 'member' and 'uniqueMember'.
 * @param string $attr_name The name of the attribute of interest (case insensitive)
 * @return bool
 */
function is_dn_attr( $attr_name )
{
    // Simple test first
    $dn_attrs = array( "aliasedObjectName" );
    foreach( $dn_attrs as $dn_attr )
        if( 0 == strcasecmp( $attr_name, $dn_attr ) )
            return true;
    // Now look at the schema OID
	$attr_schema = get_schema_attribute( $attr_name );
	if( ! $attr_schema )
		return false;
	$syntax_oid = $attr_schema->getSyntaxOID();
	if( '1.3.6.1.4.1.1466.115.121.1.12' == $syntax_oid )
		return true;
	if( '1.3.6.1.4.1.1466.115.121.1.34' == $syntax_oid )
		return true;
	$syntaxes = get_schema_syntaxes();
	if( ! isset( $syntaxes[ $syntax_oid ] ) )
		return false;
	$syntax_desc = $syntaxes[ $syntax_oid ]->getDescription();
	if( false !== strpos( strtolower($syntax_desc), 'distinguished name' ) )
		return true;
	return false;
}
function get_enc_type( $user_password )
{
    /* Capture the stuff in the { } to determine if this is crypt, md5, etc. */
    $enc_type = null;
    if( preg_match( "/{([^}]+)}/", $user_password, $enc_type) ) 
        return strtoupper($enc_type[1]); 
    else
        return null;
}
/**
 * Draw the jpegPhoto image(s) for an entry wrapped in HTML. Many options are available to 
 * specify how the images are to be displayed.
 *
 * Usage Examples:
 *  
 *    draw_jpeg_photos( 0, "cn=Bob,ou=People,dc=example,dc=com", "jpegPhoto" true, false, "border: 1px; width: 150px" );
 *    draw_jpeg_photos( 1, "cn=Fred,ou=People,dc=example,dc=com" );
 *  
 *
 * @param string $dn The DN of the entry that contains the jpeg attribute you want to draw.
 * @param string $attr_name The name of the attribute containing the jpeg data (usually 'jpegPhoto').
 * @param bool $draw_delete_buttons If true, draws a button beneath the image titled 'Delete' allowing the user
 *                  to delete the jpeg attribute by calling JavaScript function deleteAttribute() provided
 *                  in the default modification template.
 * @param bool $draw_bytes_and_size If true, draw text below the image indicating the byte size and dimensions.
 * @param string $table_html_attrs Specifies optional CSS style attributes for the table tag.
 *
 * @return void
 */
function draw_jpeg_photos( $dn, $attr_name='jpegPhoto', $draw_delete_buttons=false,
				$draw_bytes_and_size=true, $table_html_attrs='align="left"', $img_html_attrs='' )
{
	$jpeg_temp_dir = $_SESSION['lampath'] . 'tmp';
	$conn = $_SESSION['ldap']->server();
	$search_result = ldap_read( $conn, $dn, 'objectClass=*', array( $attr_name ) );
	$entry = ldap_first_entry( $conn, $search_result );
	echo "| '. _('Delete') .''; continue; } if( function_exists( 'getimagesize' ) ) { $jpeg_dimensions = @getimagesize( $jpeg_filename ); $width = $jpeg_dimensions[0]; $height = $jpeg_dimensions[1]; } else { $width = 0; $height = 0; } if( $width > 300 ) { $scale_factor = 300 / $width; $img_width = 300; $img_height = $height * $scale_factor; } else { $img_width = $width; $img_height = $height; } echo " \n"; if( $draw_bytes_and_size ) { echo "" . number_format($jpeg_data_size) . " bytes. "; echo "$width x $height pixels. \n\n"; } if( $draw_delete_buttons ) { ?> Delete Photo | 
 * $samba_users = ldap_search( 0, "(&(objectClass=sambaAccount)(objectClass=posixAccount))", 
 *                              "ou=People,dc=example,dc=com", array( "uid", "homeDirectory" ) );
 * print_r( $samba_users );
 * // prints (for example): 
 * //  Array 
 * //    ( 
 * //       [uid=jsmith,ou=People,dc=example,dc=com] => Array
 * //           (
 * //               [dn] => "uid=jsmith,ou=People,dc=example,dc=com"
 * //               [uid] => "jsmith"
 * //               [homeDirectory] => "\\server\jsmith"
 * //           )
 * //       [uid=byoung,ou=People,dc=example,dc=com] => Array
 * //           (
 * //               [dn] => "uid=byoung,ou=Samba,ou=People,dc=example,dc=com"
 * //               [uid] => "byoung"
 * //               [homeDirectory] => "\\server\byoung"
 * //           )
 * //    )
 * 
 * 
 * WARNING: This function will use a lot of memory on large searches since the entire result set is
 * stored in a single array. For large searches, you should consider sing the less memory intensive 
 * PHP LDAP API directly (ldap_search(), ldap_next_entry(), ldap_next_attribute(), etc).
 *
 * @param string $filter The LDAP filter to use when searching (example: "(objectClass=*)") (see RFC 2254)
 * @param string $base_dn The DN of the base of search. 
 * @param array $attrs An array of attributes to include in the search result (example: array( "objectClass", "uid", "sn" )).
 * @param string $scope The LDAP search scope. Must be one of "base", "one", or "sub". Standard LDAP search scope.
 * @param bool $sort_results Specify false to not sort results by DN or true to have the 
 *                  returned array sorted by DN (uses ksort)
 * @param int $deref When handling aliases or referrals, this specifies whether to follow referrals. Must be one of 
 *                  LDAP_DEREF_ALWAYS, LDAP_DEREF_NEVER, LDAP_DEREF_SEARCHING, or LDAP_DEREF_FINDING. See the PHP LDAP API for details.
 */
function pla_ldap_search( $filter, $base_dn=null, $attrs=array(), $scope='sub', $sort_results=true, $deref=LDAP_DEREF_ALWAYS )
{
	$ds = $_SESSION['ldap']->server();
	switch( $scope ) {
		case 'base':
			$search = @ldap_read( $ds, $base_dn, $filter, $attrs, 0, 0, 0, $deref );
			break;
		case 'one':
			$search = @ldap_list( $ds, $base_dn, $filter, $attrs, 0, 0, 0, $deref );
			break;
		case 'sub':
		default:
			$search = @ldap_search( $ds, $base_dn, $filter, $attrs, 0, 0, 0, $deref );
			break;
	}
	if( ! $search )
		return array();
	$return = array();
	//get the first entry identifier
	if( $entry_id = ldap_first_entry($ds,$search) )
		//iterate over the entries
		while($entry_id) {
			//get the distinguished name of the entry
			$dn = ldap_get_dn($ds,$entry_id);
			//get the attributes of the entry
			$attrs = ldap_get_attributes($ds,$entry_id);
			$return[$dn]['dn'] = $dn;
			//get the first attribute of the entry
			if($attr = ldap_first_attribute($ds,$entry_id,$attrs))
				//iterate over the attributes
				while($attr){
				  if( is_attr_binary($attr))
						$values = ldap_get_values_len($ds,$entry_id,$attr);
					else
						$values = ldap_get_values($ds,$entry_id,$attr);
					//get the number of values for this attribute
					$count = $values['count'];
					unset($values['count']);
					if($count==1)
						$return[$dn][$attr] = $values[0];
					else
						$return[$dn][$attr] = $values;
					$attr = ldap_next_attribute($ds,$entry_id,$attrs);
				}// end while attr
			$entry_id = ldap_next_entry($ds,$entry_id);
		} // end while entry_id
	if( $sort_results && is_array( $return ) )
		ksort( $return );
	return $return;
}
/**
 * Given a DN string, this returns the parent container portion of the string.
 * For example. given 'cn=Manager,dc=example,dc=com', this function returns
 * 'dc=example,dc=com'.
 * 
 * @param string $dn The DN whose container string to return.
 *
 * @return string The container
 * @see get_rdn
 */
function get_container( $dn )
{
	$parts = pla_explode_dn( $dn );
    if( count( $parts ) <= 1 )
        return null;
	$container = $parts[1];
	for( $i=2; $i