<?php /* This code is part of FusionDirectory (http://www.fusiondirectory.org/) Copyright (C) 2003-2010 Cajus Pollmeier Copyright (C) 2011-2018 FusionDirectory This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. */ /*! * \file functions.inc * Common functions and named definitions. */ /* Define common locations and variables */ require_once ('variables.inc'); /* Include required files */ require_once (CACHE_DIR.'/'.CLASS_CACHE); require_once ('functions_debug.inc'); require_once ('accept-to-gettext.inc'); /* Define constants for debugging */ define ('DEBUG_TRACE', 1); /*! Debug level for tracing of common actions (save, check, etc.) */ define ('DEBUG_LDAP', 2); /*! Debug level for LDAP queries */ define ('DEBUG_DB', 4); /*! Debug level for database operations */ define ('DEBUG_SHELL', 8); /*! Debug level for shell commands */ define ('DEBUG_POST', 16); /*! Debug level for POST content */ define ('DEBUG_SESSION', 32); /*! Debug level for SESSION content */ define ('DEBUG_CONFIG', 64); /*! Debug level for CONFIG information */ define ('DEBUG_ACL', 128); /*! Debug level for ACL infos */ define ('DEBUG_SI', 256); /*! Debug level for communication with Argonaut */ define ('DEBUG_MAIL', 512); /*! Debug level for all about mail (mailAccounts, imap, sieve etc.) */ define ('DEBUG_FAI', 1024); /* FAI (incomplete) */ /* Define shadow states */ define ('POSIX_ACCOUNT_EXPIRED', 1); define ('POSIX_WARN_ABOUT_EXPIRATION', 2); define ('POSIX_FORCE_PASSWORD_CHANGE', 4); define ('POSIX_DISALLOW_PASSWORD_CHANGE', 8); /* Rewrite german 'umlauts' and spanish 'accents' to get better results */ $REWRITE = [ "ä" => "ae", "ö" => "oe", "ü" => "ue", "Ä" => "Ae", "Ö" => "Oe", "Ü" => "Ue", "ß" => "ss", "á" => "a", "é" => "e", "í" => "i", "ó" => "o", "ú" => "u", "Á" => "A", "É" => "E", "Í" => "I", "Ó" => "O", "Ú" => "U", "ñ" => "ny", "Ñ" => "Ny" ]; /*! * \brief Does autoloading for classes used in FusionDirectory. * * Takes the list generated by 'fusiondirectory-setup' and loads the * file containing the requested class. * * \param array $class_name list of class name */ function fusiondirectory_autoload ($class_name) { global $class_mapping, $BASE_DIR, $config; if ($class_mapping === NULL) { if (isset($config) && is_object($config) && $config->get_cfg_value('displayerrors') == 'TRUE') { list($trace,) = html_trace(); echo $trace; echo "<br/>\n"; } echo sprintf(_("Fatal error: no class locations defined - please run '%s' to fix this"), "<b>fusiondirectory-setup --update-cache</b>"); exit; } /* Do not try to autoload smarty classes */ if (strpos($class_name, 'Smarty_') === 0) { return; } if (isset($class_mapping["$class_name"])) { require_once($BASE_DIR.'/'.$class_mapping["$class_name"]); } else { @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $class_name, 'Could not load'); if (isset($config) && is_object($config) && $config->get_cfg_value('displayerrors') == 'TRUE') { list($trace,) = html_trace(); echo $trace; echo "<br/>\n"; } echo sprintf(_("Fatal error: cannot instantiate class '%s' - try running '%s' to fix this"), $class_name, "<b>fusiondirectory-setup --update-cache</b>"); exit; } } spl_autoload_register('fusiondirectory_autoload'); /*! * \brief Checks if a class is available. * * \param string $name The subject of the test * * \return boolean Return TRUE if successfull FALSE otherwise */ function class_available($name) { global $class_mapping; return isset($class_mapping[$name]); } /*! * \brief Check if plugin is available * * Checks if a given plugin is available and readable. * * \param string $plugin the subject of the check * * \return boolean Return TRUE if successfull FALSE otherwise */ function plugin_available($plugin) { global $class_mapping, $BASE_DIR; if (!isset($class_mapping[$plugin])) { return FALSE; } else { return is_readable($BASE_DIR.'/'.$class_mapping[$plugin]); } } /*! * \brief Loads plist and load it in config object */ function load_plist ($ldap_available = TRUE) { global $config, $plist; if (!session::is_set('plist')) { /* Initially load all classes */ load_all_classes(); $plist = new pluglist(); session::set('plist', $plist); $config->loadPlist($plist); if ($ldap_available) { $config->get_departments(); $config->make_idepartments(); } } return session::get('plist'); } /*! * \brief Debug level action * * Print a DEBUG level if specified debug level of the level matches the * the configured debug level. * * \param int $level The log level of the message (should use the constants, * defined in functions.in (DEBUG_TRACE, DEBUG_LDAP, etc.) * * \param int $line Define the line of the logged action (using __LINE__ is common) * * \param string $function Define the function where the logged action happened in * (using __FUNCTION__ is common) * * \param string $file Define the file where the logged action happend in * (using __FILE__ is common) * * \param mixed $data The data to log. Can be a message or an array, which is printed * with print_a * * \param string $info Optional: Additional information */ function DEBUG($level, $line, $function, $file, $data, $info = '') { static $first = TRUE; if (($_SERVER['REQUEST_METHOD'] == 'POST') && preg_match('/index.php$/', $_SERVER['REQUEST_URI'])) { return; } if (session::get('DEBUGLEVEL') & $level) { if ($first) { echo '<div id="debug-handling" class="notice">'. '<img src="geticon.php?context=status&icon=dialog-information&size=22" alt="Information" style="vertical-align:middle;margin-right:.2em;"/>'. 'There is some debug output '. '<button onClick="javascript:$$(\'div.debug_div\').each(function (a) { a.toggle(); });">Toggle</button>'. '</div>'; $first = FALSE; } $output = "DEBUG[$level] "; if ($function != '') { $output .= "($file:$function():$line) - $info: "; } else { $output .= "($file:$line) - $info: "; } echo '<div class="debug_div">'; echo $output; if (is_array($data)) { print_a($data); } else { echo "'$data'"; } echo "</div>\n"; } } /*! * \brief Return themed path for specified base file * * Depending on its parameters, this function returns the full * path of a template file. First match wins while searching * in this order: * * - load theme depending file * - load global theme depending file * - load default theme file * - load global default theme file * * \param string $filename The base file name * * \param boolean $plugin Flag to take the plugin directory as search base * * \param string $path User specified path to take as search base * * \return string Full path to the template file */ function get_template_path($filename = '', $plugin = FALSE, $path = '') { global $config, $BASE_DIR; $default_theme = 'breezy'; /* Set theme */ if (isset ($config)) { $theme = $config->get_cfg_value('theme', $default_theme); } else { $theme = $default_theme; } /* Return path for empty filename */ if ($filename == '') { return "themes/$theme/"; } /* Return plugin dir or root directory? */ if ($plugin) { if ($path == '') { $path = session::get('plugin_dir'); $nf = preg_replace('!^'.$BASE_DIR.'/!', '', preg_replace('/^\.\.\//', '', $path)); } else { $nf = preg_replace('!^'.$BASE_DIR.'/!', '', $path); } $paths = [ "$BASE_DIR/ihtml/themes/$theme/$nf/$filename", "$BASE_DIR/ihtml/themes/$default_theme/$nf/$filename", "$BASE_DIR/ihtml/themes/default/$nf/$filename", $path."/$filename" ]; } else { $paths = [ "themes/$theme/$filename", "$BASE_DIR/ihtml/themes/$theme/$filename", "themes/$default_theme/$filename", "$BASE_DIR/ihtml/themes/$default_theme/$filename", "themes/default/$filename", "$BASE_DIR/ihtml/themes/default/$filename", $filename ]; } foreach ($paths as $path) { if (file_exists($path)) { return $path; } } return end($paths); } /*! * \brief Remove multiple entries from an array * * Removes every element that is in $needles from the * array given as $haystack * * \param array $needles array of the entries to remove * * \param array $haystack original array to remove the entries from */ function array_remove_entries(array $needles, array $haystack) { return array_values(array_diff($haystack, $needles)); } /*! * \brief Remove multiple entries from an array (case-insensitive) * * Removes every element that is in $needles from the * array given as $haystack but case insensitive * * \param array $needles array of the entries to remove * * \param array $haystack original array to remove the entries from */ function array_remove_entries_ics(array $needles, array $haystack) { // strcasecmp will work, because we only compare ASCII values here return array_values(array_udiff($haystack, $needles, 'strcasecmp')); } /*! * \brief Merge to array but remove duplicate entries (case-insensitive) * * Merges two arrays and removes duplicate entries. Triggers * an error if first or second parametre is not an array. * * \param array $ar1 first array * * \param array $ar2 second array * * \return array */ function array_merge_unique($ar1, $ar2) { if (!is_array($ar1) || !is_array($ar2)) { trigger_error('Specified parameter(s) are not valid arrays.'); } else { return array_values(array_unique(array_merge($ar1, $ar2))); } } /*! * \brief Generate a system log info * * Creates a syslog message, containing user information. * * \param string $message the message to log */ function fusiondirectory_log ($message) { global $ui; /* Preset to something reasonable */ $username = '[unauthenticated]'; /* Replace username if object is present */ if (isset($ui)) { if ($ui->uid != '') { $username = '['.$ui->uid.']'; } else { $username = '[unknown]'; } } syslog(LOG_INFO, "FusionDirectory $username: $message"); } /*! * \brief Initialize a LDAP connection * * Initializes a LDAP connection. * * \param string $server The server we are connecting to * * \param string $base The base of our ldap tree * * \param string $binddn Default: empty * * \param string $pass Default: empty * * \return LDAP object */ function ldap_init ($server, $base, $binddn = '', $pass = '') { global $config; $ldap = new LDAP ($binddn, $pass, $server, isset($config->current['LDAPFOLLOWREFERRALS']) && $config->current['LDAPFOLLOWREFERRALS'] == 'TRUE', isset($config->current['LDAPTLS']) && $config->current['LDAPTLS'] == 'TRUE'); /* Sadly we've no proper return values here. Use the error message instead. */ if (!$ldap->success()) { msg_dialog::display(_('Fatal error'), sprintf(_("FATAL: Error when connecting the LDAP. Server said '%s'."), $ldap->get_error()), FATAL_ERROR_DIALOG); exit(); } /* Preset connection base to $base and return to caller */ $ldap->cd ($base); return $ldap; } /*! * \brief Get user from LDAP directory * * Search the user by login or other fields authorized by the configuration * * \param string $username The username or email to check * * \return userinfo instance on SUCCESS, FALSE if not found, string error on error */ function ldap_get_user ($username) { global $config; /* look through the entire ldap */ $ldap = $config->get_ldap_link(); if (!$ldap->success()) { msg_dialog::display(_('LDAP error'), msgPool::ldaperror($ldap->get_error(), '', LDAP_AUTH).'<br/><br/>'.session::get('errors'), FATAL_ERROR_DIALOG); exit(); } $allowed_attributes = ['uid','mail']; $verify_attr = []; $tmp = explode(',', $config->get_cfg_value('loginAttribute')); foreach ($tmp as $attr) { if (in_array($attr, $allowed_attributes)) { $verify_attr[] = $attr; } } if (count($verify_attr) == 0) { $verify_attr = ['uid']; } $tmp = $verify_attr; $tmp[] = 'uid'; $filter = ''; foreach ($verify_attr as $attr) { $filter .= '('.$attr.'='.$username.')'; } $filter = '(&(|'.$filter.')(objectClass=inetOrgPerson))'; $ldap->cd($config->current['BASE']); $ldap->search($filter, $tmp); /* get results, only a count of 1 is valid */ if ($ldap->count() == 0) { /* user not found */ return FALSE; } elseif ($ldap->count() != 1) { /* found more than one matching id */ return _('Login (uid) is not unique inside the LDAP tree. Please contact your administrator.'); } /* LDAP schema is not case sensitive. Perform additional check. */ $attrs = $ldap->fetch(); $success = FALSE; foreach ($verify_attr as $attr) { if (isset($attrs[$attr][0]) && $attrs[$attr][0] == $username) { $success = TRUE; } } $ldap->disconnect(); if (!$success) { return FALSE; } return new userinfo($attrs['dn']); } /*! * \brief Verify user login against LDAP directory * * Checks if the specified username is in the LDAP and verifies if the * password is correct by binding to the LDAP with the given credentials. * * \param string $username The username to check * * \param string $password The password to check * * \return TRUE on SUCCESS, NULL or FALSE on error */ function ldap_login_user ($username, $password) { global $config; $ui = ldap_get_user($username); if ($ui === FALSE) { return NULL; } elseif (is_string($ui)) { msg_dialog::display(_('Internal error'), $ui, FATAL_ERROR_DIALOG); return NULL; } /* password check, bind as user with supplied password */ $ldapObj = new LDAP($ui->dn, $password, $config->current['SERVER'], isset($config->current['LDAPFOLLOWREFERRALS']) && $config->current['LDAPFOLLOWREFERRALS'] == 'TRUE', isset($config->current['LDAPTLS']) && $config->current['LDAPTLS'] == 'TRUE' ); $ldap = new ldapMultiplexer($ldapObj); if (!$ldap->success()) { return NULL; } if (class_available('ppolicyAccount')) { $ldap->cd($config->current['BASE']); $ldap->search('(objectClass=*)', [], 'one'); if (!$ldap->success()) { msg_dialog::display( _('Authentication error'), _('It seems your user password has expired. Please use <a href="recovery.php">password recovery</a> to change it.'), ERROR_DIALOG ); return NULL; } } /* Username is set, load subtreeACL's now */ $ui->loadACL(); return $ui; } /*! * \brief Add a lock for object(s) * * Adds a lock by the specified user for one ore multiple objects. * If the lock for that object already exists, an error is triggered. * * \param array $object The object or array of objects to lock * * \param string $user The user who shall own the lock */ function add_lock($object, $user) { global $config; /* Remember which entries were opened as read only, because we don't need to remove any locks for them later. */ if (!session::is_set('LOCK_CACHE')) { session::set('LOCK_CACHE', ['']); } if (is_array($object)) { foreach ($object as $obj) { add_lock($obj, $user); } return; } $cache = &session::get_ref('LOCK_CACHE'); if (isset($_POST['open_readonly'])) { $cache['READ_ONLY'][$object] = TRUE; return; } if (isset($cache['READ_ONLY'][$object])) { unset($cache['READ_ONLY'][$object]); } /* Just a sanity check... */ if ($object == '' || $user == '') { msg_dialog::display(_('Internal error'), _('Error while adding a lock. Contact the developers!'), ERROR_DIALOG); return; } /* Check for existing entries in lock area */ $ldap = $config->get_ldap_link(); $ldap->cd(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']); $ldap->search('(&(objectClass=fdLockEntry)(fdUserDn='.ldap_escape_f($user).')(fdObjectDn='.base64_encode($object).'))', ['fdUserDn']); if (!$ldap->success()) { msg_dialog::display(_('Configuration error'), sprintf(_('Cannot create locking information in LDAP tree. Please contact your administrator!').'<br><br>'._('LDAP server returned: %s'), '<br><br><i>'.$ldap->get_error().'</i>'), ERROR_DIALOG); return; } /* Add lock if none present */ if ($ldap->count() == 0) { $attrs = []; $name = md5($object); $ldap->cd('cn='.$name.','.get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']); $attrs = [ 'objectClass' => 'fdLockEntry', 'fdUserDn' => $user, 'fdObjectDn' => base64_encode($object), 'cn' => $name, 'fdLockTimestamp' => LdapGeneralizedTime::toString(new DateTime('now')), ]; $ldap->add($attrs); if (!$ldap->success()) { msg_dialog::display(_('LDAP error'), msgPool::ldaperror($ldap->get_error(), "cn=$name,".get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE'], 0), LDAP_ERROR); return; } } } /*! * \brief Remove a lock for object(s) * * Remove a lock for object(s) * * \param mixed $object object or array of objects for which a lock shall be removed */ function del_lock ($object) { global $config; if (is_array($object)) { foreach ($object as $obj) { del_lock($obj); } return; } /* Sanity check */ if ($object == '') { return; } /* If this object was opened in read only mode then skip removing the lock entry, there wasn't any lock created. */ if (session::is_set('LOCK_CACHE')) { $cache = &session::get_ref('LOCK_CACHE'); if (isset($cache['READ_ONLY'][$object])) { unset($cache['READ_ONLY'][$object]); return; } } /* Check for existance and remove the entry */ $ldap = $config->get_ldap_link(); $ldap->cd(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']); $ldap->search('(&(objectClass=fdLockEntry)(fdObjectDn='.base64_encode($object).'))', ['fdObjectDn']); $attrs = $ldap->fetch(); if (!$ldap->success()) { msg_dialog::display(_('LDAP error'), msgPool::ldaperror($ldap->get_error(), $ldap->getDN(), LDAP_DEL, ERROR_DIALOG)); return; } elseif (!empty($attrs['dn'])) { $ldap->rmdir($attrs['dn']); } } /*! * \brief Remove all locks owned by a specific userdn * * For a given userdn remove all existing locks. This is usually * called on logout. * * \param string $userdn the subject whose locks shall be deleted */ function del_user_locks($userdn) { global $config; /* Get LDAP ressources */ $ldap = $config->get_ldap_link(); $ldap->cd(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']); /* Remove all objects of this user, drop errors silently in this case. */ $ldap->search('(&(objectClass=fdLockEntry)(fdUserDn='.ldap_escape_f($userdn).'))', ['fdUserDn']); while ($attrs = $ldap->fetch()) { $ldap->rmdir($attrs['dn']); } } /*! * \brief Get a lock for a specific object * * Searches for a lock on a given object. * * \param string $object subject whose locks are to be searched * * \return string Returns the dn of the user who owns the lock or '' if no lock is found * or FALSE if an error occured. */ function get_lock($object) { /* Sanity check */ if ($object == '') { msg_dialog::display(_('Internal error'), _('Error while adding a lock. Contact the developers!'), ERROR_DIALOG); return FALSE; } /* Allow readonly access, the plugin constructor will restrict the acls */ if (isset($_POST['open_readonly'])) { return ''; } $locks = get_locks($object); if ($locks === FALSE) { return FALSE; } elseif (empty($locks)) { return ''; } else { return $locks[0]['user']; } } /*! * \brief Get locks for objects * * Similar as get_lock(), but for multiple objects. * * \param mixed $objects Array of dns for which a lock will be searched or dn of a single object * * \param boolean $allow_readonly TRUE if readonly access should be permitted, * FALSE if not (default). * * \return A numbered array containing all found locks as an array with key 'object' * and key 'user', or FALSE if an error occured. */ function get_locks($objects, $allow_readonly = FALSE) { global $config; if (is_array($objects) && (count($objects) == 1)) { $objects = reset($objects); } if (is_array($objects)) { if ($allow_readonly) { trigger_error('Read only is not possible for several objects'); } $filter = '(&(objectClass=fdLockEntry)(|'; foreach ($objects as $obj) { $filter .= '(fdObjectDn='.base64_encode($obj).')'; } $filter .= '))'; } else { if ($allow_readonly && isset($_POST['open_readonly'])) { /* If readonly is allowed and asked and there is only one object, bypass lock detection */ return []; } $filter = '(&(objectClass=fdLockEntry)(fdObjectDn='.base64_encode($objects).'))'; } /* Get LDAP link, check for presence of the lock entry */ $ldap = $config->get_ldap_link(); $ldap->cd(get_ou('lockRDN').get_ou('fusiondirectoryRDN').$config->current['BASE']); $ldap->search($filter, ['fdUserDn','fdObjectDn', 'fdLockTimestamp']); if (!$ldap->success()) { msg_dialog::display(_('LDAP error'), msgPool::ldaperror($ldap->get_error(), '', LDAP_SEARCH), LDAP_ERROR); return FALSE; } $locks = []; while ($attrs = $ldap->fetch()) { $locks[] = [ 'dn' => $attrs['dn'], 'object' => base64_decode($attrs['fdObjectDn'][0]), 'user' => $attrs['fdUserDn'][0], 'timestamp' => LdapGeneralizedTime::fromString($attrs['fdLockTimestamp'][0]), ]; } if (!is_array($objects) && (count($locks) > 1)) { /* Hmm. We're removing broken LDAP information here and issue a warning. */ msg_dialog::display(_('Warning'), _('Found multiple locks for object to be locked. This should not happen - cleaning up multiple references.'), WARNING_DIALOG); /* Clean up these references now... */ foreach ($locks as $lock) { $ldap->rmdir($lock['dn']); } return FALSE; } return $locks; } /*! * \brief Return the current userinfo object * * \return return the current userinfo object */ function &get_userinfo() { global $ui; return $ui; } /*! * \brief Get global smarty object * * \return return the global smarty object */ function &get_smarty() { global $smarty; return $smarty; } /*! * \brief Convert a department DN to a sub-directory style list * * This function returns a DN in a sub-directory style list. * Examples: * - ou=1.1.1,ou=limux becomes limux/1.1.1 * - cn=bla,ou=foo,dc=local becomes foo/bla or foo/bla/local, depending * on the value for $base. * * If the specified DN contains a basedn which either matches * the specified base or $config->current['BASE'] it is stripped. * * \param string $dn the subject for the conversion * * \param string $base the base dn, default: $config->current['BASE'] * * \return a string in the form as described above */ function convert_department_dn($dn, $base = NULL) { global $config; if ($base == NULL) { $base = $config->current['BASE']; } /* Build a sub-directory style list of the tree level specified in $dn */ $dn = preg_replace('/'.preg_quote($base, '/')."$/i", '', $dn); if (empty($dn)) { return '/'; } $dep = ''; foreach (explode(',', $dn) as $rdn) { $dep = preg_replace("/^[^=]+=/", '', $rdn).'/'.$dep; } /* Return and remove accidently trailing slashes */ return trim($dep, '/'); } /*! \brief Get the OU of a certain RDN * * Given a certain RDN name (ogroupRDN, applicationRDN etc.) this * function returns either a configured OU or the default * for the given RDN. * * Example: * \code * # Determine LDAP base where systems are stored * $base = get_ou('systemRDN') . $config->current['BASE']; * $ldap->cd($base); * \endcode * * \param $name the rdn of the ou you are trying to find * * \return the ou associated the the RDN or nothing * */ function get_ou($name) { global $config; $map = [ 'fusiondirectoryRDN' => 'ou=fusiondirectory,', 'lockRDN' => 'ou=locks,', 'recoveryTokenRDN' => 'ou=recovery,', 'reminderTokenRDN' => 'ou=reminder,', 'roleRDN' => 'ou=roles,', 'ogroupRDN' => 'ou=groups,', 'applicationRDN' => 'ou=apps,', 'systemRDN' => 'ou=systems,', 'serverRDN' => 'ou=servers,ou=systems,', 'terminalRDN' => 'ou=terminals,ou=systems,', 'workstationRDN' => 'ou=workstations,ou=systems,', 'printerRDN' => 'ou=printers,ou=systems,', 'phoneRDN' => 'ou=phones,ou=systems,', 'componentRDN' => 'ou=netdevices,ou=systems,', 'mobilePhoneRDN' => 'ou=mobile,ou=systems,', 'inventoryRDN' => 'ou=inventory,', 'ipmiRDN' => 'ou=ipmi,', 'faxBlocklistRDN' => 'ou=gofax,ou=systems,', 'aclRoleRDN' => 'ou=aclroles,', 'phoneMacroRDN' => 'ou=macros,ou=asterisk,ou=configs,ou=systems,', 'phoneConferenceRDN' => 'ou=conferences,ou=asterisk,ou=configs,ou=systems,', 'faiBaseRDN' => 'ou=fai,ou=configs,ou=systems,', 'faiScriptRDN' => 'ou=scripts,', 'faiHookRDN' => 'ou=hooks,', 'faiTemplateRDN' => 'ou=templates,', 'faiVariableRDN' => 'ou=variables,', 'faiProfileRDN' => 'ou=profiles,', 'faiPackageRDN' => 'ou=packages,', 'faiPartitionRDN' => 'ou=disk,', 'debconfRDN' => 'ou=debconf,', 'supannStructuresRDN' => 'ou=structures,', 'sudoRDN' => 'ou=sudoers,', 'netgroupRDN' => 'ou=netgroup,', 'deviceRDN' => 'ou=devices,', 'aliasRDN' => 'ou=alias,', 'dsaRDN' => 'ou=dsa,', 'mimetypeRDN' => 'ou=mime,' ]; /* Preset ou... */ if ($config->get_cfg_value($name, '_not_set_') != '_not_set_') { $ou = $config->get_cfg_value($name); } elseif (isset($map[$name])) { $ou = $map[$name]; return $ou; } else { return NULL; } if ($ou != '') { if (!preg_match('/^[^=]+=[^=]+/', $ou)) { $ou = "ou=$ou"; } else { $ou = "$ou"; } if (preg_match('/'.preg_quote($config->current['BASE'], '/').'$/', $ou)) { return $ou; } else { if (preg_match('/,$/', $ou)) { return $ou; } else { return "$ou,"; } } } else { return ''; } } /*! * \brief Get the OU for users * * Function for getting the userRDN * * \return the ou of the userRDN */ function get_people_ou() { return get_ou('userRDN'); } /*! \brief Return a base from a given user DN * * \code * get_base_from_people('cn=Max Muster,dc=local') * # Result is 'dc=local' * \endcode * * \param string $dn * * \return the base from the dn */ function get_base_from_people($dn) { global $config; $pattern = "/^[^,]+,".preg_quote(get_people_ou(), '/')."/i"; $base = preg_replace($pattern, '', $dn); /* Set to base, if we're not on a correct subtree */ if (!isset($config->idepartments[$base])) { $base = $config->current['BASE']; } return $base; } /*! * \brief Check if strict naming rules are configured * * Return TRUE or FALSE depending on weither strictNamingRules * are configured or not. * * \return Returns TRUE if strictNamingRules is set to TRUE or if the * config object is not available, otherwise FALSE. */ function strict_uid_mode() { global $config; if (isset($config)) { return ($config->get_cfg_value('strictNamingRules') == 'TRUE'); } return TRUE; } /*! * \brief Generate a lock message * * This message shows a warning to the user, that a certain object is locked * and presents some choices how the user can proceed. By default this * is 'Cancel' or 'Edit anyway', but depending on the function call * its possible to allow readonly access, too. * * Example usage: * \code * if ($locks = get_locks($this->dn)) { * return gen_locked_message($locks, $this->dn, TRUE); * } * \endcode * * \param string $locks the locks as returned by get_locks * * \param string $dn the locked DN * * \param boolean $allow_readonly TRUE if readonly access should be permitted, * FALSE if not (default). * * */ function gen_locked_message($locks, $dn, $allow_readonly = FALSE) { session::set('dn', $dn); $remove = FALSE; /* Save variables from LOCK_VARS_TO_USE in session - for further editing */ if ( session::is_set('LOCK_VARS_TO_USE') && count(session::get('LOCK_VARS_TO_USE'))) { $LOCK_VARS_USED_GET = []; $LOCK_VARS_USED_POST = []; $LOCK_VARS_USED_REQUEST = []; $LOCK_VARS_TO_USE = session::get('LOCK_VARS_TO_USE'); foreach ($LOCK_VARS_TO_USE as $name) { if (empty($name)) { continue; } foreach ($_POST as $Pname => $Pvalue) { if (preg_match($name, $Pname)) { $LOCK_VARS_USED_POST[$Pname] = $_POST[$Pname]; } } foreach ($_GET as $Pname => $Pvalue) { if (preg_match($name, $Pname)) { $LOCK_VARS_USED_GET[$Pname] = $_GET[$Pname]; } } foreach ($_REQUEST as $Pname => $Pvalue) { if (preg_match($name, $Pname)) { $LOCK_VARS_USED_REQUEST[$Pname] = $_REQUEST[$Pname]; } } } session::set('LOCK_VARS_TO_USE', []); session::set('LOCK_VARS_USED_GET', $LOCK_VARS_USED_GET); session::set('LOCK_VARS_USED_POST', $LOCK_VARS_USED_POST); session::set('LOCK_VARS_USED_REQUEST', $LOCK_VARS_USED_REQUEST); } /* Prepare and show template */ $smarty = get_smarty(); $smarty->assign('allow_readonly', $allow_readonly); if (is_array($dn)) { $msg = '<pre>'; foreach ($dn as $sub_dn) { $msg .= "\n".$sub_dn.', '; } $msg = preg_replace("/, $/", "</pre>", $msg); } else { $msg = $dn; } $smarty->assign('dn', $msg); if ($remove) { $smarty->assign('action', _('Continue anyway')); } else { $smarty->assign('action', _('Edit anyway')); } $smarty->assign('message', sprintf(_("You're going to edit the LDAP entry/entries %s"), "<b>".$msg."</b>", "")); $smarty->assign('locks', $locks); return $smarty->fetch(get_template_path('islocked.tpl')); } /*! * \brief Return a string/HTML representation of an array * * This returns a string representation of a given value. * It can be used to dump arrays, where every value is printed * on its own line. The output is targetted at HTML output, it uses * '<br>' for line breaks. If the value is already a string its * returned unchanged. * * \param mixed $value Whatever needs to be printed. * * \return string $value in html form. */ function to_string ($value) { /* If this is an array, generate a text blob */ if (is_array($value)) { $ret = ''; foreach ($value as $line) { $ret .= $line."<br>\n"; } return $ret; } else { return $value; } } /*! \brief Function to rewrite some problematic characters * * This function takes a string and replaces all possibly characters in it * with less problematic characters, as defined in $REWRITE. * * \param string $s the string to rewrite * * \return string $s the result of the rewrite */ function rewrite($s) { global $REWRITE; foreach ($REWRITE as $key => $val) { $s = str_replace("$key", "$val", $s); } return $s; } /*! * \brief Return the base of a given DN * * \param string $dn a DN * \param string $ou an ou to remove from the base * * \return base of the given DN */ function dn2base($dn, $ou = NULL) { if ($ou === NULL) { if (get_people_ou() != '') { $dn = preg_replace('/,'.get_people_ou().'/i', ',', $dn); } if (get_ou('groupRDN') != '') { $dn = preg_replace('/,'.get_ou('groupRDN').'/i', ',', $dn); } } else { $dn = preg_replace("/,$ou/i", ',', $dn); } return preg_replace ('/^[^,]+,/i', '', $dn); } /*! * \brief Check if a given command exists and is executable * * Test if a given cmdline contains an executable command. Strips * arguments from the given cmdline. * * \param string $cmdline the cmdline to check * * \return TRUE if command exists and is executable, otherwise FALSE. */ function check_command($cmdline) { $cmd = preg_replace("/ .*$/", '', $cmdline); /* Check if command exists in filesystem */ if (!file_exists($cmd)) { return FALSE; } /* Check if command is executable */ if (!is_executable($cmd)) { return FALSE; } return TRUE; } /*! * \brief Print plugin HTML header * * \param string $image the path of the image to be used next to the headline * * \param string $headline the headline * * \param string $info additional information to print * * \return the $display variable */ function print_header($image, $headline, $info = '') { $smarty = get_smarty(); $smarty->assign('headline', $headline); $smarty->assign('headline_image', $image); $display = ''; if ($info != '') { $display .= '<div class="pluginfo">'."\n"; $display .= "$info"; $display .= "</div>\n"; $display .= "<div></div>\n"; } return $display; } /*! * \brief Generate HTML for the 'Back' button * * \return the back button html code */ function back_to_main() { return '<br><p class="plugbottom"><input type=submit name="password_back" value="'. msgPool::backButton().'"></p><input type="hidden" name="ignore">'; } /*! * \brief Put netmask in n.n.n.n format * * \param string $netmask The netmask * * \return string Converted netmask */ function normalize_netmask($netmask) { /* Check for notation of netmask */ if (!preg_match('/^([0-9]+\.){3}[0-9]+$/', $netmask)) { $num = (int)($netmask); $netmask = ""; for ($byte = 0; $byte < 4; $byte++) { $result = 0; for ($i = 7; $i >= 0; $i--) { if ($num-- > 0) { $result += pow(2, $i); } } $netmask .= $result."."; } return preg_replace('/\.$/', '', $netmask); } return $netmask; } /*! * \brief Return the number of set bits in the netmask * * For a given subnetmask (for example 255.255.255.0) this returns * the number of set bits. * * Example: * \code * $bits = netmask_to_bits('255.255.255.0') # Returns 24 * $bits = netmask_to_bits('255.255.254.0') # Returns 23 * \endcode * * Be aware of the fact that the function does not check * if the given subnet mask is actually valid. For example: * Bad examples: * \code * $bits = netmask_to_bits('255.0.0.255') # Returns 16 * $bits = netmask_to_bits('255.255.0.255') # Returns 24 * \endcode * * \param $netmask given netmask * * \return the number of bits in the netmask */ function netmask_to_bits($netmask) { $nm = explode('.', $netmask, 4); $res = 0; for ($n = 0; $n < 4; $n++) { $start = 255; for ($i = 0; $i < 8; $i++) { if ($start == (int)($nm[$n])) { $res += 8 - $i; break; } $start -= pow(2, $i); } } return $res; } /*! * \brief Recursion helper for gen_uids() */ function _recurse_gen_uids($rule, array $variables) { $result = []; if (!count($variables)) { return [$rule]; } reset($variables); $key = key($variables); $val = current($variables); unset($variables[$key]); foreach ($val as $possibility) { $nrule = str_replace("{$key}", $possibility, $rule); $result = array_merge($result, _recurse_gen_uids($nrule, $variables)); } return $result; } /*! * \brief Generate a list of uid proposals based on a rule * * Unroll given rule string by filling in attributes and replacing * all keywords. * * \param string $rule The rule string from fusiondirectory.conf. * * \param array $attributes A dictionary of attribute/value mappings * * \return array List of valid not used uids */ function gen_uids($rule, $attributes) { global $config; // Attributes should be arrays foreach ($attributes as $name => $value) { $attributes[$name] = [$value]; } /* Search for keys and fill the variables array with all possible values for that key. */ $stripped = $rule; $variables = []; for ($pos = 0; preg_match('/%([^%]+)%/', $stripped, $m, PREG_OFFSET_CAPTURE, $pos); ) { $variables[$pos] = templateHandling::parseMask($m[1][0], $attributes); $replace = '{'.$pos.'}'; $stripped = substr_replace($stripped, $replace, $m[0][1], strlen($m[0][0])); $pos = $m[0][1] + strlen($replace); } /* Recurse through all possible combinations */ $proposed = _recurse_gen_uids($stripped, $variables); /* Get list of used ID's */ $ldap = $config->get_ldap_link(); $ldap->cd($config->current['BASE']); /* Remove used uids and watch out for id tags */ $ret = []; foreach ($proposed as $uid) { /* Check for id tag and modify uid if needed */ if (preg_match('/\{id(:|!)(\d+)}/', $uid, $m)) { $size = $m[2]; $start = ($m[1] == ":" ? 0 : -1); for ($i = $start, $p = pow(10, $size) - 1; $i < $p; $i++) { if ($i == -1) { $number = ""; } else { $number = sprintf("%0".$size."d", $i + 1); } $res = preg_replace('/{id(:|!)\d+}/', $number, $uid); $ldap->search('(uid='.ldap_escape_f(preg_replace('/[{}]/', '', $res)).')', ['dn']); if ($ldap->count() == 0) { $uid = $res; break; } } /* Remove link if nothing has been found */ $uid = preg_replace('/{id(:|!)\d+}/', '', $uid); } if (preg_match('/\{id#\d+}/', $uid)) { $size = preg_replace('/^.*{id#(\d+)}.*$/', '\\1', $uid); while (TRUE) { $number = sprintf("%0".$size."d", random_int(0, pow(10, $size) - 1)); $res = preg_replace('/{id#(\d+)}/', $number, $uid); $ldap->search('(uid='.ldap_escape_f(preg_replace('/[{}]/', '', $res)).')', ['dn']); if ($ldap->count() == 0) { $uid = $res; break; } } /* Remove link if nothing has been found */ $uid = preg_replace('/{id#\d+}/', '', $uid); } /* Don't assign used ones */ $ldap->search('(uid='.ldap_escape_f(preg_replace('/[{}]/', '', $uid)).')', ['dn']); if ($ldap->count() == 0) { /* Add uid, but remove {} first. These are invalid anyway. */ $uid = preg_replace('/[{}]/', '', $uid); if ($uid != '') { $ret[] = $uid; } } } return array_unique($ret); } /*! * \brief Convert various data sizes to bytes * * Given a certain value in the format n(g|m|k), where n * is a value and (g|m|k) stands for Gigabyte, Megabyte and Kilobyte * this function returns the byte value. * * \param string $value a value in the above specified format * * \return a byte value or the original value if specified string is simply * a numeric value */ function to_byte($value) { $value = strtolower(trim($value)); if (!is_numeric(substr($value, -1))) { switch (substr($value, -1)) { case 'g': $mult = 1073741824; break; case 'm': $mult = 1048576; break; case 'k': $mult = 1024; break; } return $mult * (int)substr($value, 0, -1); } else { return $value; } } /*! * \brief Convert a size in bytes to a human readable version * * \param float $bytes size in bytes * * \param int $precision number of digits after comma, default is 2 * * \return Returns something like '9.77KiB' for arguments (10000, 2) */ function humanReadableSize ($bytes, $precision = 2) { $format = [ _('%sB'), _('%sKiB'), _('%sMiB'), _('%sGiB'), _('%sTiB'), _('%sPiB'), _('%sEiB'), _('%sZiB'), _('%sYiB') ]; if ($bytes == 0) { return sprintf($format[0], '0'); } $base = log($bytes) / log(1024); return sprintf($format[floor($base)], round(pow(1024, $base - floor($base)), $precision)); } /*! * \brief Check if a value exists in an array (case-insensitive) * * This is just as http://php.net/in_array except that the comparison * is case-insensitive. * * \param string $value needle * * \param array $items haystack * * \return Return TRUE is value is found, FALSE if not. */ function in_array_ics($value, array $items) { return preg_grep('/^'.preg_quote($value, '/').'$/i', $items); } /*! \brief Generate a clickable alphabet */ function generate_alphabet($count = 10) { $characters = _("*ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); $alphabet = ""; $c = 0; /* Fill cells with charaters */ for ($i = 0, $l = mb_strlen($characters, 'UTF8'); $i < $l; $i++) { if ($c == 0) { $alphabet .= "<tr>"; } $ch = mb_substr($characters, $i, 1, "UTF8"); $alphabet .= "<td><a class=\"alphaselect\" href=\"main.php?plug=". validate($_GET['plug'])."&search=".$ch."\"> ".$ch." </a></td>"; if ($c++ == $count) { $alphabet .= "</tr>"; $c = 0; } } /* Fill remaining cells */ while ($c++ <= $count) { $alphabet .= "<td> </td>"; } return $alphabet; } /*! * \brief Removes malicious characters from a (POST) string. * * \param string $string the string to check for malicious caracters * * \return string with caracters removed */ function validate($string) { return strip_tags(str_replace('\0', '', $string)); } /*! \brief Recursively delete a path in the file system * * Will delete the given path and all its files recursively. * Can also follow links if told so. * * \param string $path * * \param boolean $followLinks TRUE to follow links, FALSE (default) * for not following links */ function rmdirRecursive($path, $followLinks = FALSE) { $dir = opendir($path); while ($entry = readdir($dir)) { if (is_file($path."/".$entry) || ((!$followLinks) && is_link($path."/".$entry))) { unlink($path."/".$entry); } elseif (is_dir($path."/".$entry) && ($entry != '.') && ($entry != '..')) { rmdirRecursive($path."/".$entry); } } closedir($dir); return rmdir($path); } /*! * \brief Get directory content information * * Returns the content of a directory as an array in an * ascending sorted manner. * * \param string $path * * \param boolean $sort_desc weither to sort the content descending. * * \return array content of directory in ascending sorted manner. */ function scan_directory($path, $sort_desc = FALSE) { $ret = FALSE; /* is this a dir ? */ if (is_dir($path)) { /* is this path a readable one */ if (is_readable($path)) { /* Get contents and write it into an array */ $ret = []; $dir = opendir($path); /* Is this a correct result ?*/ if ($dir) { while ($fp = readdir($dir)) { $ret[] = $fp; } } } } /* Sort array ascending , like scandir */ sort($ret); /* Sort descending if parameter is sort_desc is set */ if ($sort_desc) { $ret = array_reverse($ret); } return $ret; } /*! * \brief Clean the smarty compile dir * * \param string $directory smarty compile dir */ function clean_smarty_compile_dir($directory) { if (is_dir($directory) && is_readable($directory)) { // Set revision filename to REVISION $revision_file = $directory."/REVISION"; /* Is there a stamp containing the current revision? */ if (file_exists($revision_file)) { // check for "$config->...['CONFIG']/revision" and the // contents should match the revision number if (!compare_revision($revision_file, FD_VERSION)) { // If revision differs, clean compile directory foreach (scan_directory($directory) as $file) { if (($file == ".") || ($file == "..")) { continue; } if (is_file($directory."/".$file)) { // delete file if (!unlink($directory."/".$file)) { msg_dialog::display(_("Internal error"), sprintf(_("File '%s' could not be deleted. Try fusiondirectory-setup --check-directories to fix permissions."), $directory."/".$file), ERROR_DIALOG); } } } } else { // Revision matches, nothing to do } } /* If the file does not exists or has just been deleted */ if (!file_exists($revision_file)) { // create revision file create_revision($revision_file, FD_VERSION); } } } /*! * \brief Create the revision file * * Create the revision file in FusionDirectory spool dir * * \param string $revision_file the name of the revision file * * \param string $revision the version of FusionDirectory * * \return TRUE if successfully created FALSE otherwise */ function create_revision($revision_file, $revision) { $result = FALSE; if (is_dir(dirname($revision_file)) && is_writable(dirname($revision_file))) { if ($fh = fopen($revision_file, 'w')) { if (fwrite($fh, $revision)) { $result = TRUE; } fclose($fh); } } else { msg_dialog::display(_('Internal error'), _('Cannot write to revision file!'), ERROR_DIALOG); } return $result; } /*! * \brief Compare the revision file * * Create the revision file in FusionDirectory spool dir * * \param string $revision_file the name of the revision file * \param string $revision the version of FusionDirectory * * \return TRUE if revision match FALSE otherwise */ function compare_revision($revision_file, $revision) { // FALSE means revision differs $result = FALSE; if (file_exists($revision_file) && is_readable($revision_file)) { // Open file if ($fh = fopen($revision_file, "r")) { // Compare File contents with current revision if ($revision == fread($fh, filesize($revision_file))) { $result = TRUE; } // Close file fclose($fh); } else { msg_dialog::display(_('Internal error'), _('Cannot read revision file!'), ERROR_DIALOG); } } return $result; } /*! * \brief Lookup a key in an array case-insensitive * * Given an associative array this can lookup the value of * a certain key, regardless of the case. * * \code * $items = array ('FOO' => 'blub', 'bar' => 'blub'); * array_key_ics('foo', $items); # Returns 'blub' * array_key_ics('BAR', $items); # Returns 'blub' * \endcode * * \param string $ikey needle * * \param array $items haystack * * \return return key or empty result */ function array_key_ics($ikey, array $items) { $tmp = array_change_key_case($items, CASE_LOWER); $ikey = strtolower($ikey); if (isset($tmp[$ikey])) { return $tmp[$ikey]; } return ''; } /*! * \brief Determine if two arrays are different * * \param array $src The source * * \param array $dst The destination * * \return boolean TRUE or FALSE */ function array_differs($src, $dst) { /* If the count is differing, the arrays differ */ if (count ($src) != count ($dst)) { return TRUE; } return (count(array_diff($src, $dst)) != 0); } /*! * \brief Determine if two arrays are different using recursion for sublevels * * \param array $src The source * * \param array $dst The destination * * \return boolean TRUE or FALSE */ function array_differs_recursive($src, $dst) { if (is_array($src)) { if (!is_array($dst)) { return TRUE; } if (count($src) != count($dst)) { return TRUE; } foreach ($src as $key => $value) { if (!isset($dst[$key])) { return TRUE; } if (array_differs_recursive($dst[$key], $value)) { return TRUE; } } return FALSE; } return ((string)$src != (string)$dst); } /*! * \brief Escape all LDAP filter relevant characters * * \param string $input string where we should add \ before special caracters * */ function normalizeLdap($input) { trigger_error('deprecated, use ldap_escape_f'); return addcslashes($input, '*()\\/'); } /*! * \brief Check if LDAP schema matches the requirements * * \param string $cfg A config object */ function check_schema($cfg) { $checks = []; /* Get objectclasses */ $ldapObj = new LDAP($cfg['admin'], $cfg['password'], $cfg['connection'], FALSE, $cfg['tls']); $ldap = new ldapMultiplexer($ldapObj); $objectclasses = $ldap->get_objectclasses(TRUE); if (count($objectclasses) == 0) { msg_dialog::display(_('LDAP warning'), _('Cannot get schema information from server. No schema check possible!'), WARNING_DIALOG); return $checks; } /* This is the default block used for each entry. * to avoid unset indexes. */ $def_check = [ 'SCHEMA_FILE' => '', 'CLASSES_REQUIRED' => [], 'STATUS' => FALSE, 'IS_MUST_HAVE' => FALSE, 'MSG' => '', 'INFO' => '' ]; /* FusionDirectory core schemas */ /* core-fd */ $checks['core-fd'] = $def_check; $checks['core-fd']['SCHEMA_FILE'] = 'core-fd.schema'; $checks['core-fd']['CLASSES_REQUIRED'] = ['fdLockEntry']; $checks['core-fd']['IS_MUST_HAVE'] = TRUE; $checks['core-fd']['INFO'] = _('Main FusionDirectory schema'); /* core-fd-conf */ $checks['core-fd-conf'] = $def_check; $checks['core-fd-conf']['SCHEMA_FILE'] = 'core-fd-conf.schema'; $checks['core-fd-conf']['CLASSES_REQUIRED'] = ['fusionDirectoryConf']; $checks['core-fd-conf']['IS_MUST_HAVE'] = TRUE; $checks['core-fd-conf']['INFO'] = _('Schema used to store FusionDirectory configuration'); /* ldapns */ $checks['ldapns'] = $def_check; $checks['ldapns']['SCHEMA_FILE'] = 'ldapns.schema'; $checks['ldapns']['CLASSES_REQUIRED'] = ['hostObject']; $checks['ldapns']['IS_MUST_HAVE'] = FALSE; $checks['ldapns']['INFO'] = _('Used to store trust mode information in users or groups.'); /* template-fd */ $checks['template-fd'] = $def_check; $checks['template-fd']['SCHEMA_FILE'] = 'template-fd.schema'; $checks['template-fd']['CLASSES_REQUIRED'] = ['fdTemplate']; $checks['template-fd']['IS_MUST_HAVE'] = FALSE; $checks['template-fd']['INFO'] = _('Used to store templates.'); if (class_available('posixAccount')) { /* nis */ $checks['nis'] = $def_check; $checks['nis']['SCHEMA_FILE'] = 'nis.schema'; $checks['nis']['CLASSES_REQUIRED'] = ['posixAccount']; $checks['nis']['IS_MUST_HAVE'] = FALSE; $checks['nis']['INFO'] = _('Used to store POSIX information.'); } foreach ($checks as $name => $value) { foreach ($value['CLASSES_REQUIRED'] as $class) { if (!isset($objectclasses[$class])) { $checks[$name]['STATUS'] = FALSE; if ($value['IS_MUST_HAVE']) { $checks[$name]['MSG'] = sprintf(_('Missing required object class "%s"!'), $class); } else { $checks[$name]['MSG'] = sprintf(_('Missing optional object class "%s"!'), $class); } } else { $checks[$name]['STATUS'] = TRUE; $checks[$name]['MSG'] = sprintf(_('Class(es) available')); } } } $checks['posixGroup'] = $def_check; $checks['posixGroup']['SCHEMA_FILE'] = 'nis.schema'; $checks['posixGroup']['CLASSES_REQUIRED'] = ['posixGroup']; $checks['posixGroup']['STATUS'] = TRUE; $checks['posixGroup']['MSG'] = ''; $checks['posixGroup']['INFO'] = ''; if (isset($objectclasses['posixGroup'])) { $checks['posixGroup']['IS_MUST_HAVE'] = TRUE; /* Depending on mixed groups plugin installation status, we need different schema configurations */ if (class_available('mixedGroup') && isset($objectclasses['posixGroup']['STRUCTURAL'])) { $checks['posixGroup']['STATUS'] = FALSE; $checks['posixGroup']['MSG'] = _('You have installed the mixed groups plugin, but your schema configuration does not support this.'); $checks['posixGroup']['INFO'] = _('In order to use mixed groups the objectClass "posixGroup" must be AUXILIARY'); } elseif (!class_available('mixedGroup') && !isset($objectclasses['posixGroup']['STRUCTURAL'])) { $checks['posixGroup']['STATUS'] = FALSE; $checks['posixGroup']['MSG'] = _('Your schema is configured to support mixed groups, but this plugin is not present.'); $checks['posixGroup']['INFO'] = _('The objectClass "posixGroup" must be STRUCTURAL'); } } return $checks; } /*! * \brief Returns contents of the given POST variable and check magic quotes settings * * Depending on the magic quotes settings this returns a stripclashed'ed version of * a certain POST variable. * * \param string $name the POST var to return ($_POST[$name]) * * \return string */ function get_post($name) { if (!isset($_POST[$name])) { trigger_error("Requested POST value (".$name.") does not exists, you should add a check to prevent this message."); return FALSE; } return validate($_POST[$name]); } /*! * \brief Return class name in correct case */ function get_correct_class_name($cls) { global $class_mapping; if (isset($class_mapping) && is_array($class_mapping)) { foreach (array_keys($class_mapping) as $class) { if (preg_match("/^".$cls."$/i", $class)) { return $class; } } } return FALSE; } /*! * \brief Change the password of a given DN * * Change the password of a given DN with the specified hash. * * \param string $dn the DN whose password shall be changed * * \param string $password the password * * \param string $hash which hash to use to encrypt it, default is empty * for reusing existing hash method for this password (or use the default one). * * \return boolean TRUE on success and an error strings array on failure. */ function change_password ($dn, $password, $hash = "") { $userTabs = objects::open($dn, 'user'); $userTab = $userTabs->getBaseObject(); $userTab->userPassword = [ $hash, $password, $password, $userTab->userPassword, $userTab->attributesAccess['userPassword']->isLocked() ]; $userTabs->save_object(); $error = $userTabs->save(); if (!empty($error)) { return $error; } return TRUE; } /* Lock or unlock samba account */ function lock_samba_account($mode, array $attrs) { global $config; if (!isset($attrs['sambaNTPassword'][0])) { return []; } $modify = ['sambaNTPassword' => $attrs['sambaNTPassword'][0]]; if ($config->get_cfg_value("sambaGenLMPassword", "FALSE") == "TRUE") { $modify['sambaLMPassword'] = $attrs['sambaLMPassword'][0]; } else { $modify['sambaLMPassword'] = []; } foreach ($modify as &$pwd) { if (is_array($pwd)) { continue; } if ($mode == 'LOCK') { /* Lock entry */ if (!preg_match('/^\!/', $pwd)) { $pwd = '!'.$pwd; } } else { /* Unlock entry */ $pwd = preg_replace("/^\!/", "", $pwd); } } unset($pwd); return $modify; } /* Lock or unlock ssh account */ function lock_ssh_account($mode, array $attrs, &$modify) { if (!isset($attrs['sshPublicKey'])) { return; } $modify['sshPublicKey'] = []; for ($i = 0; $i < $attrs['sshPublicKey']['count']; ++$i) { if ($mode == 'LOCK') { $modify['sshPublicKey'][] = preg_replace('/^/', 'disabled-', $attrs['sshPublicKey'][$i]); } else { $modify['sshPublicKey'][] = preg_replace('/^disabled-/', '', $attrs['sshPublicKey'][$i]); } } } /*! * \brief Get the Change Sequence Number of a certain DN * * To verify if a given object has been changed outside of FusionDirectory * in the meanwhile, this function can be used to get the entryCSN * from the LDAP directory. It uses the attribute as configured * in modificationDetectionAttribute * * \param string $dn The dn you want to check * * \return either the result or "" in any other case */ function getEntryCSN($dn) { global $config; if (empty($dn) || !is_object($config)) { return ''; } /* Get attribute that we should use as serial number */ $attr = $config->get_cfg_value('modificationDetectionAttribute'); if ($attr != '') { $ldap = $config->get_ldap_link(); $ldap->cat($dn, [$attr]); $csn = $ldap->fetch(); if (isset($csn[$attr][0])) { return $csn[$attr][0]; } } return ''; } /*! * \brief Initialize a file download with given content, name and data type. * * \param string $data The content to send. * * \param string $name The name of the file. * * \param string $type The content identifier, default value is "application/octet-stream"; */ function send_binary_content($data, $name, $type = "application/octet-stream") { header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT"); header("Cache-Control: no-cache"); header("Pragma: no-cache"); header("Cache-Control: post-check=0, pre-check=0"); header("Content-type: ".$type); /* Strip name if it is a complete path */ if (preg_match ("/\//", $name)) { $name = basename($name); } /* force download dialog */ header('Content-Disposition: attachment; filename="'.$name.'"'); echo $data; exit(); } function reverse_html_entities($str, $type = ENT_QUOTES, $charset = "UTF-8") { if (is_string($str)) { return htmlentities($str, $type, $charset); } elseif (is_array($str)) { foreach ($str as $name => $value) { $str[$name] = reverse_html_entities($value, $type, $charset); } } return $str; } /*! * \brief Encode special string characters * * Encode the special caracters so we can use the string in * HTML output, without breaking quotes. * * \param string $str The String we want to encode. * * \return string The encoded String */ function xmlentities($str) { if (is_string($str)) { return htmlspecialchars($str, ENT_QUOTES); } elseif (is_array($str)) { foreach ($str as $name => $value) { $str[$name] = xmlentities($value); } } return $str; } /*! * \brief Returns a random char */ function get_random_char () { $randno = rand (0, 63); if ($randno < 12) { // Digits, '/' and '.' return chr($randno + 46); } elseif ($randno < 38) { // Uppercase return chr($randno + 53); } else { // Lowercase return chr($randno + 59); } } /*! * \brief Decrypt a string with RIJNDAEL_128 * * \param string $input The string to decrypt. * * \param String $password The password used */ function cred_decrypt($input, $password) { /************************* Inspired by Crypt/CBC.pm *******************************/ $input = pack('H*', $input); if (substr($input, 0, 8) != 'Salted__') { throw new FusionDirectoryException("Invalid hash header: expected 'Salted__', found '".substr($input, 0, 8)."'"); } $salt = substr($input, 8, 8); $input = substr($input, 16); $key_len = 32; $iv_len = openssl_cipher_iv_length('aes-256-cbc'); $data = ''; $d = ''; while (strlen($data) < $key_len + $iv_len) { $d = md5($d . $password . $salt, TRUE); $data .= $d; } $key = substr($data, 0, $key_len); $iv = substr($data, $key_len, $iv_len); return openssl_decrypt($input, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv); } function get_object_info() { return session::get('objectinfo'); } function set_object_info($str = "") { session::set('objectinfo', $str); } /*! * \brief Test if an ip is the network range * * \param string $ip The ip address to test. * * \param string $net The network to test * * \param string $mask The netmask of the network */ function isIpInNet($ip, $net, $mask) { // Move to long ints $ip = ip2long($ip); $net = ip2long($net); $mask = ip2long($mask); // Mask given IP with mask. If it returns "net", we're in... return (($ip & $mask) == $net); } /*! * \brief Expands an IP v6 */ function expandIPv6 ($ip) { $hex = unpack('H*hex', inet_pton($ip)); $ip = substr(preg_replace('/([A-f0-9]{4})/', "$1:", $hex['hex']), 0, -1); return $ip; } /* Mark the occurance of a string with a span */ function mark($needle, $haystack) { $result = ''; while (preg_match('/^(.*)('.preg_quote($needle).')(.*)$/i', $haystack, $matches)) { $result .= $matches[1].'<span class="mark">'.$matches[2].'</span>'; $haystack = $matches[3]; } return $result.$haystack; } function reset_errors() { session::set('errors', ''); session::set('errorsAlreadyPosted', []); session::set('LastError', ''); } function load_all_classes() { global $BASE_DIR, $class_list, $class_mapping; /* Initially load all classes */ $class_list = get_declared_classes(); foreach ($class_mapping as $class => $path) { if (!in_array($class, $class_list)) { if (is_readable("$BASE_DIR/$path")) { require_once("$BASE_DIR/$path"); } else { msg_dialog::display(_('Fatal error'), sprintf(_("Cannot locate file '%s' - please run '%s' to fix this"), "$BASE_DIR/$path", '<b>fusiondirectory-setup</b>'), FATAL_ERROR_DIALOG); exit; } } } } if (!function_exists('ldap_escape')) { /* This bloc is for PHP<5.6 */ define('LDAP_ESCAPE_FILTER', 0x01); define('LDAP_ESCAPE_DN', 0x02); /* PHP version of ldap_escape for PHP<5.6 */ function ldap_escape($subject, $ignore = '', $flags = 0) { static $charMaps = [ LDAP_ESCAPE_FILTER => ['\\', '*', '(', ')', "\x00"], LDAP_ESCAPE_DN => ['\\', ',', '=', '+', '<', '>', ';', '"', '#'], ]; // Pre-process the char maps on first call if (!isset($charMaps[0])) { $charMaps[0] = []; for ($i = 0; $i < 256; $i++) { $charMaps[0][chr($i)] = sprintf('\\%02x', $i); } for ($i = 0, $l = count($charMaps[LDAP_ESCAPE_FILTER]); $i < $l; $i++) { $chr = $charMaps[LDAP_ESCAPE_FILTER][$i]; unset($charMaps[LDAP_ESCAPE_FILTER][$i]); $charMaps[LDAP_ESCAPE_FILTER][$chr] = $charMaps[0][$chr]; } for ($i = 0, $l = count($charMaps[LDAP_ESCAPE_DN]); $i < $l; $i++) { $chr = $charMaps[LDAP_ESCAPE_DN][$i]; unset($charMaps[LDAP_ESCAPE_DN][$i]); $charMaps[LDAP_ESCAPE_DN][$chr] = $charMaps[0][$chr]; } } // Create the base char map to escape $flags = (int)$flags; $charMap = []; if ($flags & LDAP_ESCAPE_FILTER) { $charMap += $charMaps[LDAP_ESCAPE_FILTER]; } if ($flags & LDAP_ESCAPE_DN) { $charMap += $charMaps[LDAP_ESCAPE_DN]; } if (!$charMap) { $charMap = $charMaps[0]; } // Remove any chars to ignore from the list $ignore = (string)$ignore; for ($i = 0, $l = strlen($ignore); $i < $l; $i++) { unset($charMap[$ignore[$i]]); } // Do the main replacement $result = strtr($subject, $charMap); // Encode leading/trailing spaces if LDAP_ESCAPE_DN is passed if ($flags & LDAP_ESCAPE_DN) { if ($result[0] === ' ') { $result = '\\20' . substr($result, 1); } if ($result[strlen($result) - 1] === ' ') { $result = substr($result, 0, -1) . '\\20'; } } return $result; } } if (!function_exists('random_int')) { // PHP<7, we fallback on openssl_random_pseudo_bytes function random_int($min, $max) { $range = $max - $min; if ($range <= 0) { throw new Exception('Invalid range passed to random_int'); } $log = log($range, 2); // length in bytes $nbBytes = (int) ($log / 8) + 1; // length in bits $nbBits = (int) $log + 1; // set all lower bits to 1 $filter = pow(2, $nbBits) - 1; if ($filter >= PHP_INT_MAX) { $filter = PHP_INT_MAX; } do { $randomBytes = openssl_random_pseudo_bytes($nbBytes, $strong); if (!$strong || ($randomBytes === FALSE)) { throw new Exception('Failed to get random bytes'); } $rnd = hexdec(bin2hex($randomBytes)); // discard irrelevant bits $rnd = $rnd & $filter; } while ($rnd > $range); return $min + $rnd; } } function ldap_escape_f($str, $ignore = '') { return ldap_escape($str, $ignore, LDAP_ESCAPE_FILTER); } function ldap_escape_dn($str, $ignore = '') { return ldap_escape($str, $ignore, LDAP_ESCAPE_DN); } function mail_utf8($to, $from_user, $from_email, $subject, $message, $replyto_user = NULL, $replyto_email = NULL, $type = 'plain') { $subject = "=?UTF-8?B?".base64_encode($subject)."?="; if ($replyto_user === NULL) { $replyto_user = $from_user; } if ($replyto_email === NULL) { $replyto_email = $from_email; } if ($from_user) { $from_user = "=?UTF-8?B?".base64_encode($from_user)."?="; $headers = "From: $from_user <$from_email>\r\n"; } else { $headers = "From: <$from_email>\r\n"; } if ($replyto_email) { if ($replyto_user) { $replyto_user = "=?UTF-8?B?".base64_encode($replyto_user)."?="; $headers .= "Reply-To: $replyto_user <$replyto_email>\r\n"; } else { $headers .= "Reply-To: <$replyto_email>\r\n"; } } $headers .= "MIME-Version: 1.0" . "\r\n" . "Content-type: text/$type; charset=UTF-8" . "\r\n"; $additional_parameters = "-f".$from_email; return mail($to, $subject, $message, $headers, $additional_parameters); } /* Calls fopen, gives errors as an array if any, file handle if successful */ function fopenWithErrorHandling() { $args = func_get_args(); $errors = []; set_error_handler( function ($errno, $errstr, $errfile, $errline, $errcontext) use (&$errors) { $errors[] = $errstr; } ); $fh = @call_user_func_array('fopen', $args); restore_error_handler(); if ($fh !== FALSE) { return $fh; } return $errors; } ?>