, 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 ''; echo ''; echo '( '; echo '' . _('Refresh') . ''; if (checkIfWriteAccessIsAllowed()) { echo ' | '; echo '' . _('Create new entry') . ''; } echo ' )'; // Fetch and display the base DN for this server $base_dn = $_SESSION['config']->get_Suffix('tree'); // Did we get a base_dn for this server somehow? if( $base_dn ) { echo "\n\n\n\n"; // is the root of the tree expanded already? if( isset( $tree[$base_dn] ) ) { $expand_href = "collapse.php?" . "dn=" . rawurlencode( $base_dn ); $expand_img = "../../graphics/minus.png"; $expand_alt = "-"; $child_count = number_format( count( $tree[$base_dn] ) ); } else { $expand_href = "expand.php?" . "dn=" . rawurlencode( $base_dn ); $expand_img = "../../graphics/plus.png"; $expand_alt = "+"; $child_count = count( get_container_contents( $base_dn, 0, '(objectClass=*)') ); if( $child_count > $limit ) $child_count = $limit . '+'; } $edit_href = "edit.php?dn=" . rawurlencode( $base_dn ); $icon = isset( $tree_icons[ $base_dn ] ) ? $tree_icons[ $base_dn ] : get_icon( $base_dn ); echo ""; echo "\"$expand_alt\""; echo ""; echo "\"img\"\n"; echo "" . pretty_print_dn( $base_dn ) . ''; if( $child_count ) echo " ($child_count)"; echo "\n"; echo "\n"; if(isset($tree[ $base_dn ]) && count( $tree[ $base_dn ] ) > 10 ) draw_create_link( $base_dn, -1, urlencode( $base_dn )); } flush(); // Is the root of the tree expanded already? if( isset( $tree[$base_dn] ) && is_array( $tree[$base_dn] ) ) { foreach( $tree[ $base_dn ] as $child_dn ) draw_tree_html( $child_dn, 0 ); if (checkIfWriteAccessIsAllowed()) { echo ''; echo '' . 
							_('Create new entry') . ''; echo '' . _('Create new entry') . ''; } } } /** * Checks and fixes an initial session's tree cache if needed. * * This function is not meant as a user-callable function, but rather a convenient, * automated method for checking the initial data structure of the session. */ function initialize_session_tree() { // From the PHP manual: If you use $_SESSION don't use // session_register(), session_is_registered() or session_unregister()! if( ! array_key_exists( 'tree', $_SESSION ) ) $_SESSION['tree'] = array(); if( ! array_key_exists( 'tree_icons', $_SESSION ) ) $_SESSION['tree_icons'] = build_initial_tree_icons(); // Make sure that the tree index is indeed well formed. if( ! is_array( $_SESSION['tree'] ) ) $_SESSION['tree'] = array(); if( ! is_array( $_SESSION['tree_icons'] ) ) $_SESSION['tree_icons'] = build_initial_tree_icons(); } /** * Builds the initial array that stores the icon-lookup for each server's DN in the tree browser. The returned * array is then stored in the current session. The structure of the returned array is simple, and looks like * this: * * 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 count($dn2_parts) ) { return 1; } elseif( count( $dn2_parts ) > count( $dn1_parts ) ) { return -1; } else { return 0; } } /** * Reverses a DN such that the top-level RDN is first and the bottom-level RDN is last * For example: * * 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 "
\n\n"; // for each jpegPhoto in the entry, draw it (there may be only one, and that's okay) $jpeg_data = @ldap_get_values_len( $conn, $entry, $attr_name ); if( ! is_array( $jpeg_data ) ) { echo "Could not fetch jpeg data from LDAP server for attribute " . htmlspecialchars( $attr_name ); return; } for( $i=0; $i<$jpeg_data['count']; $i++ ) { // ensures that the photo is written to the specified jpeg_temp_dir $jpeg_temp_dir = realpath($jpeg_temp_dir.'/'); $jpeg_filename = $jpeg_temp_dir . '/' . 'jpg' . $_SESSION['ldap']->new_rand() . '.jpg'; $outjpeg = @fopen($jpeg_filename, "wb"); fwrite($outjpeg, $jpeg_data[$i]); fclose ($outjpeg); $jpeg_data_size = filesize( $jpeg_filename ); if( $jpeg_data_size < 6 && $draw_delete_buttons ) { echo _('jpegPhoto contains errors'); 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
\n\n"; } /** * A handy ldap searching function very similar to PHP's ldap_search() with the * following exceptions: Callers may specify a search scope and the return value * is an array containing the search results rather than an LDAP result resource. * * Example usage: * * $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