An error occurred while loading the file. Please try again.
-
Côme Chilliet authored
Some resources are turned into objects, others may follow, we might as well stop using is_resource right away. issue #6175
Unverifiedac244d74
<?php
/*
This code is part of FusionDirectory (http://www.fusiondirectory.org/)
Copyright (C) 2003-2010 Cajus Pollmeier
Copyright (C) 2011-2016 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');
/*!
* \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 (strpos($class_name, 'FusionDirectory\\') === 0) {
$class_name = preg_replace('/^.+\\\\([^\\\\]+)$/', '\\1', "$class_name");
}
if (isset($class_mapping["$class_name"])) {
require_once($BASE_DIR.'/'.$class_mapping["$class_name"]);
} else {
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
logging::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 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
*/
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
function DEBUG ($level, $line, $function, $file, $data, $info = '')
{
logging::debug($level, $line, $function, $file, $data, $info);
}
/*!
* \brief Return HTML safe copyright notice
*/
function copynotice ()
{
return sprintf(htmlescape(_('%s 2002-%d %sThe FusionDirectory team, %s%s')), '©', date('Y'), '<a href="http://www.fusiondirectory.org">', FD_VERSION, '</a>');
}
/*!
* \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",
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
"themes/default/$filename",
"$BASE_DIR/ihtml/themes/default/$filename",
$filename
];
}
foreach ($paths as $path) {
if (file_exists($path)) {
return $path;
}
}
return end($paths);
}
/**
* 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
*
* @return array<int,mixed>
*/
function array_remove_entries (array $needles, array $haystack): array
{
return array_values(array_udiff($haystack, $needles, 'array_cmp_recursive'));
}
/**
* 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<int|string|bool|null|float|double|object> $needles array of the entries to remove
*
* @param array<int|string|bool|null|float|double|object> $haystack original array to remove the entries from
*
* @return array<int,int|string|bool|null|float|double|object>
*/
function array_remove_entries_ics (array $needles, array $haystack): array
{
// strcasecmp will work, because we only compare ASCII values here
return array_values(array_udiff($haystack, $needles, 'strcasecmp'));
}
/**
* 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<int|string|bool|null|float|double|object> $ar1 first array
*
* @param array<int|string|bool|null|float|double|object> $ar2 second array
*
* @return array<int,int|string|bool|null|float|double|object>
*/
function array_merge_unique (array $ar1, array $ar2): array
{
return array_values(array_unique(array_merge($ar1, $ar2)));
}
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
/*!
* \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 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)
{
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
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,',
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
'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])) {
return $map[$name];
} 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
*
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
* \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 */
$departmentInfo = $config->getDepartmentInfo();
if (!isset($departmentInfo[$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 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;
}
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
}
/*! \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)
{
/* Rewrite german 'umlauts' and spanish 'accents'
to get better results */
static $REWRITE = [
'ä' => 'ae',
'ö' => 'oe',
'ü' => 'ue',
'Ä' => 'Ae',
'Ö' => 'Oe',
'Ü' => 'Ue',
'ß' => 'ss',
'á' => 'a',
'é' => 'e',
'í' => 'i',
'ó' => 'o',
'ú' => 'u',
'Á' => 'A',
'É' => 'E',
'Í' => 'I',
'Ó' => 'O',
'Ú' => 'U',
'ñ' => 'ny',
'Ñ' => 'Ny',
];
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);
}
/*!
631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
* \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 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 += 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
701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
*
* 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 -= 2 ** $i;
}
}
return $res;
}
/*!
* \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;
default:
return $value;
}
return $mult * (int)substr($value, 0, -1);
} else {
return $value;
}
}
771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
/*!
* \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(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 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
841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
*/
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 ? */
/* is this path a readable one */
if (is_dir($path) && 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";
911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980
/* 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) && !unlink($directory.'/'.$file)) {
$error = new FusionDirectoryError(
htmlescape(sprintf(
_('File "%s" could not be deleted. Try "fusiondirectory-setup --check-directories" to fix permissions.'),
$directory."/".$file
))
);
$error->display();
}
}
} 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))) {
$fh = fopenWithErrorHandling($revision_file, 'w');
if (is_array($fh)) {
$error = new FusionDirectoryError(
htmlescape(_('Cannot write to revision file:')).'<br/>'.
implode(
'<br/>',
array_map('htmlescape', $fh)
)
);
$error->display();
return $result;
} else {
if (fwrite($fh, $revision)) {
$result = TRUE;
}
fclose($fh);
}
}
if (!$result) {
$error = new FusionDirectoryError(htmlescape(_('Cannot write to revision file!')));
$error->display();
981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
}
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
$fh = fopenWithErrorHandling($revision_file, 'r');
if (is_array($fh)) {
$error = new FusionDirectoryError(
htmlescape(_('Cannot read revision file:')).'<br/>'.
implode(
'<br/>',
array_map('htmlescape', $fh)
)
);
$error->display();
} else {
// Compare File contents with current revision
if ($revision == fread($fh, filesize($revision_file))) {
$result = TRUE;
}
// Close file
fclose($fh);
}
}
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];
1051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
}
return '';
}
/**
* Determine if two arrays are different
*
* @param array<int|string|bool|null|float|double|object> $src The source
*
* @param array<int|string|bool|null|float|double|object> $dst The destination
*
* @return bool TRUE or FALSE
*/
function array_differs (array $src, array $dst): bool
{
/* If the count is differing, the arrays differ */
if (count($src) != count($dst)) {
return TRUE;
}
return (count(array_diff($src, $dst)) != 0);
}
/**
* Determine if two arrays are different using recursion for sublevels
*
* @param mixed $src The source
*
* @param mixed $dst The destination
*
* @return bool TRUE or FALSE
*/
function array_differs_recursive ($src, $dst): bool
{
return (array_cmp_recursive($src, $dst) !== 0);
}
/**
* Determine if two arrays are different using recursion for sublevels
*
* @param mixed $src The source
*
* @param mixed $dst The destination
*
* @return int negative, 0 or positive if $src is <, = or > $dst
*/
function array_cmp_recursive ($src, $dst): int
{
if (is_array($src)) {
if (!is_array($dst)) {
return 1;
}
if (count($src) != count($dst)) {
return count($src) - count($dst);
}
foreach ($src as $key => $value) {
if (!isset($dst[$key])) {
return 1;
}
if (($cmp = array_cmp_recursive($dst[$key], $value)) !== 0) {
return $cmp;
}
}
return 0;
}
return strcmp($src, $dst);
}
1121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
/*!
* \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 (array $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) {
$warning = new FusionDirectoryWarning(htmlescape(_('Cannot get schema information from server. No schema check possible!')));
$warning->display();
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 */
1191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260
$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
*/
1261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330
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->update();
$error = $userTabs->save();
if (!empty($error)) {
return $error;
}
return TRUE;
}
/*!
* \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
1331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400
*
* \return either the result or "" in any other case
*/
function getEntryCSN (string $dn): string
{
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]);
$attrs = $ldap->fetch();
if (isset($attrs[$attr][0])) {
return $attrs[$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();
}
/*!
* \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);
1401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
}
}
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);
}
/*!
* \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);
1471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540
// 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));
return substr(preg_replace('/([A-f0-9]{4})/', "$1:", $hex['hex']), 0, -1);
}
/* 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('errorsAlreadyPosted', []);
}
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 {
throw new FatalError(
sprintf(
htmlescape(_('Cannot locate file "%s" - please run "%s" to fix this')),
htmlescape("$BASE_DIR/$path"),
'<b>fusiondirectory-setup --update-cache</b>'
)
);
}
}
}
}
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;
154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609
}
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)
{
$errors = [];
set_error_handler(
function (int $errno, string $errstr, string $errfile, int $errline, array $errcontext) use (&$errors): bool
{
$errors[] = $errstr;
return TRUE;
}
);
$fh = @fopen(...$args);
restore_error_handler();
if ($fh !== FALSE) {
return $fh;
}
return $errors;
}
// Check to see if it exists in case PHP has this function later
if (!function_exists('mb_substr_replace')) {
// Same parameters as substr_replace with the extra encoding parameter
function mb_substr_replace (string $string, string $replacement, $start, $length = NULL, $encoding = NULL)
{
if ($encoding === NULL) {
$encoding = mb_internal_encoding();
}
if ($length === NULL) {
return mb_substr($string, 0, $start, $encoding).
$replacement;
} else {
return mb_substr($string, 0, $start, $encoding).
$replacement.
mb_substr($string, $start + $length, NULL, $encoding);
}
}
}