1 <?php
2 /*
3 * This code is part of GOsa (http://www.gosa-project.org)
4 * Copyright (C) 2003-2008 GONICUS GmbH
5 *
6 * ID: $$Id: functions.inc 13100 2008-12-01 14:07:48Z hickert $$
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 */
23 /*! \file
24 * Common functions and named definitions. */
26 /* Define globals for revision comparing */
27 $svn_path = '$HeadURL$';
28 $svn_revision = '$Revision$';
30 /* Configuration file location */
31 if(!isset($_SERVER['CONFIG_DIR'])){
32 define ("CONFIG_DIR", "/etc/gosa");
33 }else{
34 define ("CONFIG_DIR",$_SERVER['CONFIG_DIR']);
35 }
37 /* Allow setting the config file in the apache configuration
38 e.g. SetEnv CONFIG_FILE gosa.conf.2.6
39 */
40 if(!isset($_SERVER['CONFIG_FILE'])){
41 define ("CONFIG_FILE", "gosa.conf");
42 }else{
43 define ("CONFIG_FILE",$_SERVER['CONFIG_FILE']);
44 }
46 /* Define common locatitions */
47 define ("CONFIG_TEMPLATE_DIR", "../contrib");
48 define ("TEMP_DIR","/var/cache/gosa/tmp");
50 /* Define get_list flags */
51 define("GL_NONE", 0);
52 define("GL_SUBSEARCH", 1);
53 define("GL_SIZELIMIT", 2);
54 define("GL_CONVERT", 4);
55 define("GL_NO_ACL_CHECK", 8);
57 /* Heimdal stuff */
58 define('UNIVERSAL',0x00);
59 define('INTEGER',0x02);
60 define('OCTET_STRING',0x04);
61 define('OBJECT_IDENTIFIER ',0x06);
62 define('SEQUENCE',0x10);
63 define('SEQUENCE_OF',0x10);
64 define('SET',0x11);
65 define('SET_OF',0x11);
66 define('DEBUG',false);
67 define('HDB_KU_MKEY',0x484442);
68 define('TWO_BIT_SHIFTS',0x7efc);
69 define('DES_CBC_CRC',1);
70 define('DES_CBC_MD4',2);
71 define('DES_CBC_MD5',3);
72 define('DES3_CBC_MD5',5);
73 define('DES3_CBC_SHA1',16);
75 /* Include required files */
76 include_once("class_location.inc");
77 require_once ("functions_debug.inc");
78 require_once ("accept-to-gettext.inc");
80 /* Define constants for debugging */
81 define ("DEBUG_TRACE", 1); /*! Debug level for tracing of common actions (save, check, etc.) */
82 define ("DEBUG_LDAP", 2); /*! Debug level for LDAP queries */
83 define ("DEBUG_MYSQL", 4); /*! Debug level for mysql operations */
84 define ("DEBUG_SHELL", 8); /*! Debug level for shell commands */
85 define ("DEBUG_POST", 16); /*! Debug level for POST content */
86 define ("DEBUG_SESSION",32); /*! Debug level for SESSION content */
87 define ("DEBUG_CONFIG", 64); /*! Debug level for CONFIG information */
88 define ("DEBUG_ACL", 128); /*! Debug level for ACL infos */
89 define ("DEBUG_SI", 256); /*! Debug level for communication with gosa-si */
90 define ("DEBUG_MAIL", 512); /*! Debug level for all about mail (mailAccounts, imap, sieve etc.) */
91 define ("DEBUG_FAI", 1024); // FAI (incomplete)
92 define ("DEBUG_RPC", 2048); /*! Debug level for communication with remote procedures */
94 // Define shadow states
95 define ("POSIX_ACCOUNT_EXPIRED", 1);
96 define ("POSIX_WARN_ABOUT_EXPIRATION", 2);
97 define ("POSIX_FORCE_PASSWORD_CHANGE", 4);
98 define ("POSIX_DISALLOW_PASSWORD_CHANGE", 8);
100 /* Rewrite german 'umlauts' and spanish 'accents'
101 to get better results */
102 $REWRITE= array( "ä" => "ae",
103 "ö" => "oe",
104 "ü" => "ue",
105 "Ä" => "Ae",
106 "Ö" => "Oe",
107 "Ü" => "Ue",
108 "ß" => "ss",
109 "á" => "a",
110 "é" => "e",
111 "í" => "i",
112 "ó" => "o",
113 "ú" => "u",
114 "Á" => "A",
115 "É" => "E",
116 "Í" => "I",
117 "Ó" => "O",
118 "Ú" => "U",
119 "ñ" => "ny",
120 "Ñ" => "Ny" );
122 /*! \brief Cyrillic (russian) fonetic transliteration (converts russian letters to ASCII and backward according to GOST 7.79-2000 )
123 * \param string 'str' Source string in russian codepage
124 * \return string Translitered string value.
125 */
126 function cyrillic2ascii($str) {
127 $ru = array('а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я',
128 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'H', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я',
129 'ґ', 'є', 'ї', 'Ґ', 'Є', 'Ї'
130 );
131 $en = array('a', 'b', 'v', 'g', 'd', 'e', 'jo','zh','z', 'i', 'jj','k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'f', 'kh','c', 'ch','sh','shh','"', 'y', '\'','eh','ju','ja',
132 'A', 'B', 'V', 'G', 'D', 'E', 'Jo','Je','Z', 'I', 'Jj','K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'F', 'Kh','C', 'CH','SH','Shh','"', 'Y', '\'','Eh','Ju','Ja',
133 'g', 'ye','yi','G', 'Ye','Yi'
134 );
136 return str_replace($ru, $en, $str);
137 }
140 /*! \brief Does autoloading for classes used in GOsa.
141 *
142 * Takes the list generated by 'update-gosa' and loads the
143 * file containing the requested class.
144 *
145 * \param string 'class_name' The currently requested class
146 */
147 function __gosa_autoload($class_name) {
148 global $class_mapping, $BASE_DIR;
150 if ($class_mapping === NULL){
151 echo sprintf(_("Fatal error: no class locations defined - please run %s to fix this"), bold("update-gosa"));
152 exit;
153 }
155 if (isset($class_mapping["$class_name"])){
156 require_once($BASE_DIR."/".$class_mapping["$class_name"]);
157 } else {
158 echo sprintf(_("Fatal error: cannot instantiate class %s - try running %s to fix this"), bold($class_name), bold("update-gosa"));
159 exit;
160 }
161 }
162 spl_autoload_register('__gosa_autoload');
165 /*! \brief Checks if a class is available.
166 * \param string 'name' The subject of the test
167 * \return boolean True if class is available, else false.
168 */
169 function class_available($name)
170 {
171 global $class_mapping, $config;
173 $disabled = array();
174 if($config instanceOf config && $config->configRegistry instanceOf configRegistry){
175 $disabled = $config->configRegistry->getDisabledPlugins();
176 }
178 return(isset($class_mapping[$name]) && !isset($disabled[$name]));
179 }
182 /*! \brief Check if plugin is available
183 *
184 * Checks if a given plugin is available and readable.
185 *
186 * \param string 'plugin' the subject of the check
187 * \return boolean True if plugin is available, else FALSE.
188 */
189 function plugin_available($plugin)
190 {
191 global $class_mapping, $BASE_DIR;
193 if (!isset($class_mapping[$plugin])){
194 return false;
195 } else {
196 return is_readable($BASE_DIR."/".$class_mapping[$plugin]);
197 }
198 }
201 /*! \brief Create seed with microseconds
202 *
203 * Example:
204 * \code
205 * srand(make_seed());
206 * $random = rand();
207 * \endcode
208 *
209 * \return float a floating point number which can be used to feed srand() with it
210 * */
211 function make_seed() {
212 list($usec, $sec) = explode(' ', microtime());
213 return (float) $sec + ((float) $usec * 100000);
214 }
217 /*! \brief DEBUG level action
218 *
219 * print a DEBUG level if specified debug level of the level matches the
220 * the configured debug level.
221 *
222 * \param int 'level' The log level of the message (should use the constants,
223 * defined in functions.in (DEBUG_TRACE, DEBUG_LDAP, etc.)
224 * \param int 'line' Define the line of the logged action (using __LINE__ is common)
225 * \param string 'function' Define the function where the logged action happened in
226 * (using __FUNCTION__ is common)
227 * \param string 'file' Define the file where the logged action happend in
228 * (using __FILE__ is common)
229 * \param mixed 'data' The data to log. Can be a message or an array, which is printed
230 * with print_a
231 * \param string 'info' Optional: Additional information
232 *
233 * */
234 function DEBUG($level, $line, $function, $file, $data, $info="")
235 {
236 global $config;
237 $debugLevel = 0;
238 if($config instanceOf config){
239 $debugLevel = $config->get_cfg_value('core', 'debugLevel');
240 }
241 if ($debugLevel & $level){
242 $output= "DEBUG[$level] ";
243 if ($function != ""){
244 $output.= "($file:$function():$line) - $info: ";
245 } else {
246 $output.= "($file:$line) - $info: ";
247 }
248 echo $output;
249 if (is_array($data)){
250 print_a($data);
251 } else {
252 echo "'$data'";
253 }
254 echo "<br>";
255 }
256 }
259 /*! \brief Determine which language to show to the user
260 *
261 * Determines which language should be used to present gosa content
262 * to the user. It does so by looking at several possibilites and returning
263 * the first setting that can be found.
264 *
265 * -# Language configured by the user
266 * -# Global configured language
267 * -# Language as returned by al2gt (as configured in the browser)
268 *
269 * \return string gettext locale string
270 */
271 function get_browser_language()
272 {
273 /* Try to use users primary language */
274 global $config;
275 $ui= get_userinfo();
276 if (isset($ui) && $ui !== NULL){
277 if ($ui->language != ""){
278 return ($ui->language.".UTF-8");
279 }
280 }
282 /* Check for global language settings in gosa.conf */
283 if (isset ($config) && $config->get_cfg_value("core",'language') != ""){
284 $lang = $config->get_cfg_value("core",'language');
285 if(!preg_match("/utf/i",$lang)){
286 $lang .= ".UTF-8";
287 }
288 return($lang);
289 }
291 /* Load supported languages */
292 $gosa_languages= get_languages();
294 /* Move supported languages to flat list */
295 $langs= array();
296 foreach($gosa_languages as $lang => $dummy){
297 $langs[]= $lang.'.UTF-8';
298 }
300 /* Return gettext based string */
301 return (al2gt($langs, 'text/html'));
302 }
305 /*! \brief Rewrite ui object to another dn
306 *
307 * Usually used when a user is renamed. In this case the dn
308 * in the user object must be updated in order to point
309 * to the correct DN.
310 *
311 * \param string 'dn' the old DN
312 * \param string 'newdn' the new DN
313 * */
314 function change_ui_dn($dn, $newdn)
315 {
316 $ui= session::global_get('ui');
317 if ($ui->dn == $dn){
318 $ui->dn= $newdn;
319 session::global_set('ui',$ui);
320 }
321 }
324 /*! \brief Return themed path for specified base file
325 *
326 * Depending on its parameters, this function returns the full
327 * path of a template file. First match wins while searching
328 * in this order:
329 *
330 * - load theme depending file
331 * - load global theme depending file
332 * - load default theme file
333 * - load global default theme file
334 *
335 * \param string 'filename' The base file name
336 * \param boolean 'plugin' Flag to take the plugin directory as search base
337 * \param string 'path' User specified path to take as search base
338 * \return string Full path to the template file
339 */
340 function get_template_path($filename= '', $plugin= FALSE, $path= "")
341 {
342 global $config, $BASE_DIR;
344 /* Set theme */
345 if (isset ($config)){
346 $theme= $config->get_cfg_value("core","theme");
347 } else {
348 $theme= "default";
349 }
351 /* Return path for empty filename */
352 if ($filename == ''){
353 return ("themes/$theme/");
354 }
356 /* Return plugin dir or root directory? */
357 if ($plugin){
358 if ($path == ""){
359 $nf= preg_replace("!^".$BASE_DIR."/!", "", preg_replace('/^\.\.\//', '', session::global_get('plugin_dir')));
360 } else {
361 $nf= preg_replace("!^".$BASE_DIR."/!", "", $path);
362 }
363 if (file_exists("$BASE_DIR/ihtml/themes/$theme/$nf")){
364 return ("$BASE_DIR/ihtml/themes/$theme/$nf/$filename");
365 }
366 if (file_exists("$BASE_DIR/ihtml/themes/default/$nf")){
367 return ("$BASE_DIR/ihtml/themes/default/$nf/$filename");
368 }
369 if ($path == ""){
370 return (session::global_get('plugin_dir')."/$filename");
371 } else {
372 return ($path."/$filename");
373 }
374 } else {
375 if (file_exists("themes/$theme/$filename")){
376 return ("themes/$theme/$filename");
377 }
378 if (file_exists("$BASE_DIR/ihtml/themes/$theme/$filename")){
379 return ("$BASE_DIR/ihtml/themes/$theme/$filename");
380 }
381 if (file_exists("themes/default/$filename")){
382 return ("themes/default/$filename");
383 }
384 if (file_exists("$BASE_DIR/ihtml/themes/default/$filename")){
385 return ("$BASE_DIR/ihtml/themes/default/$filename");
386 }
387 return ($filename);
388 }
389 }
392 /*! \brief Remove multiple entries from an array
393 *
394 * Removes every element that is in $needles from the
395 * array given as $haystack
396 *
397 * \param array 'needles' array of the entries to remove
398 * \param array 'haystack' original array to remove the entries from
399 */
400 function array_remove_entries($needles, $haystack)
401 {
402 return (array_merge(array_diff($haystack, $needles)));
403 }
406 /*! \brief Remove multiple entries from an array (case-insensitive)
407 *
408 * Same as array_remove_entries(), but case-insensitive. */
409 function array_remove_entries_ics($needles, $haystack)
410 {
411 // strcasecmp will work, because we only compare ASCII values here
412 return (array_merge(array_udiff($haystack, $needles, 'strcasecmp')));
413 }
416 /*! Merge to array but remove duplicate entries
417 *
418 * Merges two arrays and removes duplicate entries. Triggers
419 * an error if first or second parametre is not an array.
420 *
421 * \param array 'ar1' first array
422 * \param array 'ar2' second array-
423 * \return array
424 */
425 function gosa_array_merge($ar1,$ar2)
426 {
427 if(!is_array($ar1) || !is_array($ar2)){
428 trigger_error("Specified parameter(s) are not valid arrays.");
429 }else{
430 return(array_values(array_unique(array_merge($ar1,$ar2))));
431 }
432 }
435 /*! \brief Generate a system log info
436 *
437 * Creates a syslog message, containing user information.
438 *
439 * \param string 'message' the message to log
440 * */
441 function gosa_log ($message)
442 {
443 global $ui;
445 /* Preset to something reasonable */
446 $username= "[unauthenticated]";
448 /* Replace username if object is present */
449 if (isset($ui)){
450 if ($ui->username != ""){
451 $username= "[$ui->username]";
452 } else {
453 $username= "[unknown]";
454 }
455 }
457 syslog(LOG_INFO,"GOsa$username: $message");
458 }
461 /*! \brief Initialize a LDAP connection
462 *
463 * Initializes a LDAP connection.
464 *
465 * \param string 'server'
466 * \param string 'base'
467 * \param string 'binddn' Default: empty
468 * \param string 'pass' Default: empty
469 *
470 * \return LDAP object
471 */
472 function ldap_init ($server, $base, $binddn='', $pass='')
473 {
474 global $config;
476 $ldap = new LDAP ($binddn, $pass, $server,
477 isset($config->current['LDAPFOLLOWREFERRALS']) && $config->current['LDAPFOLLOWREFERRALS'] == "true",
478 isset($config->current['LDAPTLS']) && $config->current['LDAPTLS'] == "true");
480 /* Sadly we've no proper return values here. Use the error message instead. */
481 if (!$ldap->success()){
482 msg_dialog::display(_("Fatal error"),
483 sprintf(_("Error while connecting to LDAP: %s"), $ldap->get_error()),
484 FATAL_ERROR_DIALOG);
485 exit();
486 }
488 /* Preset connection base to $base and return to caller */
489 $ldap->cd ($base);
490 return $ldap;
491 }
494 /* \brief Process htaccess authentication */
495 function process_htaccess ($username, $kerberos= FALSE)
496 {
497 global $config;
499 /* Search for $username and optional @REALM in all configured LDAP trees */
500 foreach($config->data["LOCATIONS"] as $name => $data){
502 $config->set_current($name);
503 $mode= "kerberos";
504 if ($config->get_cfg_value("core","useSaslForKerberos") == "true"){
505 $mode= "sasl";
506 }
508 /* Look for entry or realm */
509 $ldap= $config->get_ldap_link();
510 if (!$ldap->success()){
511 msg_dialog::display(_("LDAP error"),
512 msgPool::ldaperror($ldap->get_error(), "", LDAP_AUTH)."<br><br>".session::get('errors'),
513 FATAL_ERROR_DIALOG);
514 exit();
515 }
516 $ldap->search("(&(objectClass=gosaAccount)(|(uid=$username)(userPassword={$mode}$username)))", array("uid"));
518 /* Found a uniq match? Return it... */
519 if ($ldap->count() == 1) {
520 $attrs= $ldap->fetch();
521 return array("username" => $attrs["uid"][0], "server" => $name);
522 }
523 }
525 /* Nothing found? Return emtpy array */
526 return array("username" => "", "server" => "");
527 }
530 /*! \brief Verify user login against htaccess
531 *
532 * Checks if the specified username is available in apache, maps the user
533 * to an LDAP user. The password has been checked by apache already.
534 *
535 * \param string 'username'
536 * \return
537 * - TRUE on SUCCESS, NULL or FALSE on error
538 */
539 function ldap_login_user_htaccess ($username)
540 {
541 global $config;
543 /* Look for entry or realm */
544 $ldap= $config->get_ldap_link();
545 if (!$ldap->success()){
546 msg_dialog::display(_("LDAP error"),
547 msgPool::ldaperror($ldap->get_error(), "", LDAP_AUTH)."<br><br>".session::get('errors'),
548 FATAL_ERROR_DIALOG);
549 exit();
550 }
551 $ldap->search("(&(objectClass=gosaAccount)(uid=$username))", array("uid"));
552 /* Found no uniq match? Strange, because we did above... */
553 if ($ldap->count() != 1) {
554 msg_dialog::display(_("LDAP error"), _("User ID is not unique!"), FATAL_ERROR_DIALOG);
555 return (NULL);
556 }
557 $attrs= $ldap->fetch();
559 /* got user dn, fill acl's */
560 $ui= new userinfo($config, $ldap->getDN());
561 $ui->username= $attrs['uid'][0];
563 /* Bail out if we have login restrictions set, for security reasons
564 the message is the same than failed user/pw */
565 if (!$ui->loginAllowed()){
566 new log("security","login","",array(),"Login restriction for user \"$username\", login not permitted");
567 return (NULL);
568 }
570 /* No password check needed - the webserver did it for us */
571 $ldap->disconnect();
573 /* Username is set, load subtreeACL's now */
574 $ui->loadACL();
576 /* TODO: check java script for htaccess authentication */
577 session::global_set('js', true);
579 return ($ui);
580 }
583 /*! \brief Verify user login against LDAP directory
584 *
585 * Checks if the specified username is in the LDAP and verifies if the
586 * password is correct by binding to the LDAP with the given credentials.
587 *
588 * \param string 'username'
589 * \param string 'password'
590 * \return
591 * - TRUE on SUCCESS, NULL or FALSE on error
592 */
593 function ldap_login_user ($username, $password)
594 {
595 global $config;
597 /* look through the entire ldap */
598 $ldap = $config->get_ldap_link();
599 if (!$ldap->success()){
600 msg_dialog::display(_("LDAP error"),
601 msgPool::ldaperror($ldap->get_error(), "", LDAP_AUTH)."<br><br>".session::get('errors'),
602 FATAL_ERROR_DIALOG);
603 exit();
604 }
605 $ldap->cd($config->current['BASE']);
606 $allowed_attributes = array("uid","mail");
607 $verify_attr = array();
608 if($config->get_cfg_value("core","loginAttribute") != ""){
609 $tmp = explode(",", $config->get_cfg_value("core","loginAttribute"));
610 foreach($tmp as $attr){
611 if(in_array_strict($attr,$allowed_attributes)){
612 $verify_attr[] = $attr;
613 }
614 }
615 }
616 if(count($verify_attr) == 0){
617 $verify_attr = array("uid");
618 }
619 $tmp= $verify_attr;
620 $tmp[] = "uid";
621 $filter = "";
622 foreach($verify_attr as $attr) {
623 $filter.= "(".$attr."=".$username.")";
624 }
625 $filter = "(&(|".$filter.")(objectClass=gosaAccount))";
626 $ldap->search($filter,$tmp);
628 /* get results, only a count of 1 is valid */
629 switch ($ldap->count()){
631 /* user not found */
632 case 0: return (NULL);
634 /* valid uniq user */
635 case 1:
636 break;
638 /* found more than one matching id */
639 default:
640 msg_dialog::display(_("Internal error"), _("User ID is not unique!"), FATAL_ERROR_DIALOG);
641 return (NULL);
642 }
644 /* LDAP schema is not case sensitive. Perform additional check. */
645 $attrs= $ldap->fetch();
646 $success = FALSE;
647 foreach($verify_attr as $attr){
648 if(isset($attrs[$attr][0]) && $attrs[$attr][0] == $username){
649 $success = TRUE;
650 }
651 }
652 if(!$success){
653 return(FALSE);
654 }
656 /* got user dn, fill acl's */
657 $ui= new userinfo($config, $ldap->getDN());
658 $ui->username= $attrs['uid'][0];
660 /* Bail out if we have login restrictions set, for security reasons
661 the message is the same than failed user/pw */
662 if (!$ui->loginAllowed()){
663 new log("security","login","",array(),"Login restriction for user \"$username\", login not permitted");
664 return (NULL);
665 }
667 /* password check, bind as user with supplied password */
668 $ldap->disconnect();
669 $ldap= new LDAP($ui->dn, $password, $config->current['SERVER'],
670 isset($config->current['LDAPFOLLOWREFERRALS']) &&
671 $config->current['LDAPFOLLOWREFERRALS'] == "true",
672 isset($config->current['LDAPTLS'])
673 && $config->current['LDAPTLS'] == "true");
674 if (!$ldap->success()){
675 return (NULL);
676 }
678 /* Username is set, load subtreeACL's now */
679 $ui->loadACL();
681 return ($ui);
682 }
685 /*! \brief Checks the posixAccount status by comparing the shadow attributes.
686 *
687 * @param Object The GOsa configuration object.
688 * @param String The 'dn' of the user to test the account status for.
689 * @param String The 'uid' of the user we're going to test.
690 * @return Const
691 * POSIX_ACCOUNT_EXPIRED - If the account is expired.
692 * POSIX_WARN_ABOUT_EXPIRATION - If the account is going to expire.
693 * POSIX_FORCE_PASSWORD_CHANGE - The password has to be changed.
694 * POSIX_DISALLOW_PASSWORD_CHANGE - The password cannot be changed right now.
695 *
696 *
697 *
698 * shadowLastChange
699 * |
700 * |---- shadowMin ---> | <-- shadowMax --
701 * | | |
702 * |------- shadowWarning -> |
703 * |-- shadowInactive --> DEACTIVATED
704 * |
705 * EXPIRED
706 *
707 */
708 function ldap_expired_account($config, $userdn, $uid)
709 {
710 // Skip this for the admin account, we do not want to lock him out.
711 if($uid == 'admin') return(0);
713 $ldap= $config->get_ldap_link();
714 $ldap->cd($config->current['BASE']);
715 $ldap->cat($userdn);
716 $attrs= $ldap->fetch();
717 $current= floor(date("U") /60 /60 /24);
719 // Fetch required attributes
720 foreach(array('shadowExpire','shadowLastChange','shadowMax','shadowMin',
721 'shadowInactive','shadowWarning','sambaKickoffTime') as $attr){
722 $$attr = (isset($attrs[$attr][0]))? $attrs[$attr][0] : null;
723 }
726 // Check if the account has reached its kick off limitations.
727 // ---------------------------------------------------------
728 // Once the accout reaches the kick off limit it has expired.
729 if($sambaKickoffTime !== null){
730 if(time() >= $sambaKickoffTime){
731 return(POSIX_ACCOUNT_EXPIRED);
732 }
733 }
736 // Check if the account has expired.
737 // ---------------------------------
738 // An account is locked/expired once its expiration date has reached (shadowExpire).
739 // If the optional attribute (shadowInactive) is set, we've to postpone
740 // the account expiration by the amount of days specified in (shadowInactive).
741 if($shadowExpire != null && $shadowExpire <= $current){
743 // The account seems to be expired, but we've to check 'shadowInactive' additionally.
744 // ShadowInactive specifies an amount of days we've to reprieve the user.
745 // It some kind of x days' grace.
746 if($shadowInactive == null || $current > $shadowExpire + $shadowInactive){
748 // Finally we've detect that the account is deactivated.
749 return(POSIX_ACCOUNT_EXPIRED);
750 }
751 }
753 // The users password is going to expire.
754 // --------------------------------------
755 // We've to warn the user in the case of an expiring account.
756 // An account is going to expire when it reaches its expiration date (shadowExpire).
757 // The user has to be warned, if the days left till expiration, match the
758 // configured warning period (shadowWarning)
759 // --> shadowWarning: Warn x days before account expiration.
760 if($shadowExpire != null && $shadowWarning != null){
762 // Check if the account is still active and not already expired.
763 if($shadowExpire >= $current){
765 // Check if we've to warn the user by comparing the remaining
766 // number of days till expiration with the configured amount
767 // of days in shadowWarning.
768 if(($shadowExpire - $current) <= $shadowWarning){
769 return(POSIX_WARN_ABOUT_EXPIRATION);
770 }
771 }
772 }
774 // -- I guess this is the correct detection, isn't it?
775 if($shadowLastChange != null && $shadowWarning != null && $shadowMax != null){
776 $daysRemaining = ($shadowLastChange + $shadowMax) - $current ;
777 if($daysRemaining > 0 && $daysRemaining <= $shadowWarning){
778 return(POSIX_WARN_ABOUT_EXPIRATION);
779 }
780 }
783 // Check if we've to force the user to change his password.
784 // --------------------------------------------------------
785 // A password change is enforced when the password is older than
786 // the configured amount of days (shadowMax).
787 // The age of the current password (shadowLastChange) plus the maximum
788 // amount amount of days (shadowMax) has to be smaller than the
789 // current timestamp.
790 if($shadowLastChange != null && $shadowMax != null){
792 // Check if we've an outdated password.
793 if($current >= ($shadowLastChange + $shadowMax)){
794 return(POSIX_FORCE_PASSWORD_CHANGE);
795 }
796 }
799 // Check if we've to freeze the users password.
800 // --------------------------------------------
801 // Once a user has changed his password, he cannot change it again
802 // for a given amount of days (shadowMin).
803 // We should not allow to change the password within GOsa too.
804 if($shadowLastChange != null && $shadowMin != null){
806 // Check if we've an outdated password.
807 if(($shadowLastChange + $shadowMin) >= $current){
808 return(POSIX_DISALLOW_PASSWORD_CHANGE);
809 }
810 }
812 return(0);
813 }
817 /*! \brief Add a lock for object(s)
818 *
819 * Adds a lock by the specified user for one ore multiple objects.
820 * If the lock for that object already exists, an error is triggered.
821 *
822 * \param mixed 'object' object or array of objects to lock
823 * \param string 'user' the user who shall own the lock
824 * */
825 function add_lock($object, $user)
826 {
827 global $config;
829 /* Remember which entries were opened as read only, because we
830 don't need to remove any locks for them later.
831 */
832 if(!session::global_is_set("LOCK_CACHE")){
833 session::global_set("LOCK_CACHE",array(""));
834 }
835 if(is_array($object)){
836 foreach($object as $obj){
837 add_lock($obj,$user);
838 }
839 return;
840 }
842 $cache = &session::global_get("LOCK_CACHE");
843 if(isset($_POST['open_readonly'])){
844 $cache['READ_ONLY'][$object] = TRUE;
845 return;
846 }
847 if(isset($cache['READ_ONLY'][$object])){
848 unset($cache['READ_ONLY'][$object]);
849 }
852 /* Just a sanity check... */
853 if ($object == "" || $user == ""){
854 msg_dialog::display(_("Internal error"), _("Error while locking entry!"), ERROR_DIALOG);
855 return;
856 }
858 /* Check for existing entries in lock area */
859 $ldap= $config->get_ldap_link();
860 $ldap->cd ($config->get_cfg_value("core","config"));
861 $ldap->search("(&(objectClass=gosaLockEntry)(gosaUser=$user)(gosaObject=".base64_encode($object)."))",
862 array("gosaUser"));
863 if (!$ldap->success()){
864 msg_dialog::display(_("Configuration error"), sprintf(_("Cannot store lock information in LDAP!")."<br><br>"._('Error: %s'), "<br><br><i>".$ldap->get_error()."</i>"), ERROR_DIALOG);
865 return;
866 }
868 /* Add lock if none present */
869 if ($ldap->count() == 0){
870 $attrs= array();
871 $name= md5($object);
872 $ldap->cd("cn=$name,".$config->get_cfg_value("core","config"));
873 $attrs["objectClass"] = "gosaLockEntry";
874 $attrs["gosaUser"] = $user;
875 $attrs["gosaObject"] = base64_encode($object);
876 $attrs["cn"] = "$name";
877 $ldap->add($attrs);
878 if (!$ldap->success()){
879 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), "cn=$name,".$config->get_cfg_value("core","config"), 0, ERROR_DIALOG));
880 return;
881 }
882 }
883 }
886 /*! \brief Remove a lock for object(s)
887 *
888 * Does the opposite of add_lock().
889 *
890 * \param mixed 'object' object or array of objects for which a lock shall be removed
891 * */
892 function del_lock ($object)
893 {
894 global $config;
896 if(is_array($object)){
897 foreach($object as $obj){
898 del_lock($obj);
899 }
900 return;
901 }
903 /* Sanity check */
904 if ($object == ""){
905 return;
906 }
908 /* If this object was opened in read only mode then
909 skip removing the lock entry, there wasn't any lock created.
910 */
911 if(session::global_is_set("LOCK_CACHE")){
912 $cache = &session::global_get("LOCK_CACHE");
913 if(isset($cache['READ_ONLY'][$object])){
914 unset($cache['READ_ONLY'][$object]);
915 return;
916 }
917 }
919 /* Check for existance and remove the entry */
920 $ldap= $config->get_ldap_link();
921 $ldap->cd ($config->get_cfg_value("core","config"));
922 $ldap->search ("(&(objectClass=gosaLockEntry)(gosaObject=".base64_encode($object)."))", array("gosaObject"));
923 $attrs= $ldap->fetch();
924 if ($ldap->getDN() != "" && $ldap->success()){
925 $ldap->rmdir ($ldap->getDN());
927 if (!$ldap->success()){
928 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $ldap->getDN(), LDAP_DEL, ERROR_DIALOG));
929 return;
930 }
931 }
932 }
935 /*! \brief Remove all locks owned by a specific userdn
936 *
937 * For a given userdn remove all existing locks. This is usually
938 * called on logout.
939 *
940 * \param string 'userdn' the subject whose locks shall be deleted
941 */
942 function del_user_locks($userdn)
943 {
944 global $config;
946 /* Get LDAP ressources */
947 $ldap= $config->get_ldap_link();
948 $ldap->cd ($config->get_cfg_value("core","config"));
950 /* Remove all objects of this user, drop errors silently in this case. */
951 $ldap->search("(&(objectClass=gosaLockEntry)(gosaUser=$userdn))", array("gosaUser"));
952 while ($attrs= $ldap->fetch()){
953 $ldap->rmdir($attrs['dn']);
954 }
955 }
958 /*! \brief Get a lock for a specific object
959 *
960 * Searches for a lock on a given object.
961 *
962 * \param string 'object' subject whose locks are to be searched
963 * \return string Returns the user who owns the lock or "" if no lock is found
964 * or an error occured.
965 */
966 function get_lock ($object)
967 {
968 global $config;
970 /* Sanity check */
971 if ($object == ""){
972 msg_dialog::display(_("Internal error"), _("Error while locking entry!"), ERROR_DIALOG);
973 return("");
974 }
976 /* Allow readonly access, the plugin::plugin will restrict the acls */
977 if(isset($_POST['open_readonly'])) return("");
979 /* Get LDAP link, check for presence of the lock entry */
980 $user= "";
981 $ldap= $config->get_ldap_link();
982 $ldap->cd ($config->get_cfg_value("core","config"));
983 $ldap->search("(&(objectClass=gosaLockEntry)(gosaObject=".base64_encode($object)."))", array("gosaUser"));
984 if (!$ldap->success()){
985 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), "", LDAP_SEARCH, ERROR_DIALOG));
986 return("");
987 }
989 /* Check for broken locking information in LDAP */
990 if ($ldap->count() > 1){
992 /* Clean up these references now... */
993 while ($attrs= $ldap->fetch()){
994 $ldap->rmdir($attrs['dn']);
995 }
997 return("");
999 } elseif ($ldap->count() == 1){
1000 $attrs = $ldap->fetch();
1001 $user= $attrs['gosaUser'][0];
1002 }
1003 return ($user);
1004 }
1007 /*! Get locks for multiple objects
1008 *
1009 * Similar as get_lock(), but for multiple objects.
1010 *
1011 * \param array 'objects' Array of Objects for which a lock shall be searched
1012 * \return A numbered array containing all found locks as an array with key 'dn'
1013 * and key 'user' or "" if an error occured.
1014 */
1015 function get_multiple_locks($objects)
1016 {
1017 global $config;
1019 if(is_array($objects)){
1020 $filter = "(&(objectClass=gosaLockEntry)(|";
1021 foreach($objects as $obj){
1022 $filter.="(gosaObject=".base64_encode($obj).")";
1023 }
1024 $filter.= "))";
1025 }else{
1026 $filter = "(&(objectClass=gosaLockEntry)(gosaObject=".base64_encode($objects)."))";
1027 }
1029 /* Get LDAP link, check for presence of the lock entry */
1030 $user= "";
1031 $ldap= $config->get_ldap_link();
1032 $ldap->cd ($config->get_cfg_value("core","config"));
1033 $ldap->search($filter, array("gosaUser","gosaObject"));
1034 if (!$ldap->success()){
1035 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), "", LDAP_SEARCH, ERROR_DIALOG));
1036 return("");
1037 }
1039 $users = array();
1040 while($attrs = $ldap->fetch()){
1041 $dn = base64_decode($attrs['gosaObject'][0]);
1042 $user = $attrs['gosaUser'][0];
1043 $users[] = array("dn"=> $dn,"user"=>$user);
1044 }
1045 return ($users);
1046 }
1049 /*! \brief Search base and sub-bases for all objects matching the filter
1050 *
1051 * This function searches the ldap database. It searches in $sub_bases,*,$base
1052 * for all objects matching the $filter.
1053 * \param string 'filter' The ldap search filter
1054 * \param string 'category' The ACL category the result objects belongs
1055 * \param string 'sub_bases' The sub base we want to search for e.g. "ou=apps"
1056 * \param string 'base' The ldap base from which we start the search
1057 * \param array 'attributes' The attributes we search for.
1058 * \param long 'flags' A set of Flags
1059 */
1060 function get_sub_list($filter, $category,$sub_deps, $base= "", $attributes= array(), $flags= GL_SUBSEARCH)
1061 {
1062 global $config, $ui;
1063 $departments = array();
1065 # $start = microtime(TRUE);
1067 /* Get LDAP link */
1068 $ldap= $config->get_ldap_link($flags & GL_SIZELIMIT);
1070 /* Set search base to configured base if $base is empty */
1071 if ($base == ""){
1072 $base = $config->current['BASE'];
1073 }
1074 $ldap->cd ($base);
1076 /* Ensure we have an array as department list */
1077 if(is_string($sub_deps)){
1078 $sub_deps = array($sub_deps);
1079 }
1081 /* Remove ,.*$ ("ou=1,ou=2.." => "ou=1") */
1082 $sub_bases = array();
1083 foreach($sub_deps as $key => $sub_base){
1084 if(empty($sub_base)){
1086 /* Subsearch is activated and we got an empty sub_base.
1087 * (This may be the case if you have empty people/group ous).
1088 * Fall back to old get_list().
1089 * A log entry will be written.
1090 */
1091 if($flags & GL_SUBSEARCH){
1092 $sub_bases = array();
1093 break;
1094 }else{
1096 /* Do NOT search within subtrees is requeste and the sub base is empty.
1097 * Append all known departments that matches the base.
1098 */
1099 $departments[$base] = $base;
1100 }
1101 }else{
1102 $sub_bases[$key] = preg_replace("/,.*$/","",$sub_base);
1103 }
1104 }
1106 /* If there is no sub_department specified, fall back to old method, get_list().
1107 */
1108 if(!count($sub_bases) && !count($departments)){
1110 /* Log this fall back, it may be an unpredicted behaviour.
1111 */
1112 if(!count($sub_bases) && !count($departments)){
1113 // log($action,$objecttype,$object,$changes_array = array(),$result = "")
1114 new log("debug","all",__FILE__,$attributes,
1115 sprintf("get_sub_list(): Falling back to get_list(), due to empty sub_bases parameter.".
1116 " This may slow down GOsa. Used filter: %s", $filter));
1117 }
1118 $tmp = get_list($filter, $category,$base,$attributes,$flags);
1119 return($tmp);
1120 }
1122 /* Get all deparments matching the given sub_bases */
1123 $base_filter= "";
1124 foreach($sub_bases as $sub_base){
1125 $base_filter .= "(".$sub_base.")";
1126 }
1127 $base_filter = "(&(objectClass=organizationalUnit)(|".$base_filter."))";
1128 $ldap->search($base_filter,array("dn"));
1129 while($attrs = $ldap->fetch()){
1130 foreach($sub_deps as $sub_dep){
1132 /* Only add those departments that match the reuested list of departments.
1133 *
1134 * e.g. sub_deps = array("ou=servers,ou=systems,");
1135 *
1136 * In this case we have search for "ou=servers" and we may have also fetched
1137 * departments like this "ou=servers,ou=blafasel,..."
1138 * Here we filter out those blafasel departments.
1139 */
1140 if(preg_match("/".preg_quote($sub_dep, '/')."/",$attrs['dn'])){
1141 $departments[$attrs['dn']] = $attrs['dn'];
1142 break;
1143 }
1144 }
1145 }
1147 $result= array();
1148 $limit_exceeded = FALSE;
1150 /* Search in all matching departments */
1151 foreach($departments as $dep){
1153 /* Break if the size limit is exceeded */
1154 if($limit_exceeded){
1155 return($result);
1156 }
1158 $ldap->cd($dep);
1160 /* Perform ONE or SUB scope searches? */
1161 if ($flags & GL_SUBSEARCH) {
1162 $ldap->search ($filter, $attributes);
1163 } else {
1164 $ldap->ls ($filter,$dep,$attributes);
1165 }
1167 /* Check for size limit exceeded messages for GUI feedback */
1168 if (preg_match("/size limit/i", $ldap->get_error())){
1169 session::set('limit_exceeded', TRUE);
1170 $limit_exceeded = TRUE;
1171 }
1173 /* Crawl through result entries and perform the migration to the
1174 result array */
1175 while($attrs = $ldap->fetch()) {
1176 $dn= $ldap->getDN();
1178 /* Convert dn into a printable format */
1179 if ($flags & GL_CONVERT){
1180 $attrs["dn"]= convert_department_dn($dn);
1181 } else {
1182 $attrs["dn"]= $dn;
1183 }
1185 /* Skip ACL checks if we are forced to skip those checks */
1186 if($flags & GL_NO_ACL_CHECK){
1187 $result[]= $attrs;
1188 }else{
1190 /* Sort in every value that fits the permissions */
1191 if (!is_array($category)){
1192 $category = array($category);
1193 }
1194 foreach ($category as $o){
1195 if((preg_match("/\//",$o) && preg_match("/r/",$ui->get_permissions($dn,$o))) ||
1196 (!preg_match("/\//",$o) && preg_match("/r/",$ui->get_category_permissions($dn, $o)))){
1197 $result[]= $attrs;
1198 break;
1199 }
1200 }
1201 }
1202 }
1203 }
1204 # if(microtime(TRUE) - $start > 0.1){
1205 # echo sprintf("<pre>GET_SUB_LIST %s .| %f --- $base -----$filter ---- $flags</pre>",__LINE__,microtime(TRUE) - $start);
1206 # }
1207 return($result);
1208 }
1211 /*! \brief Search base for all objects matching the filter
1212 *
1213 * Just like get_sub_list(), but without sub base search.
1214 * */
1215 function get_list($filter, $category, $base= "", $attributes= array(), $flags= GL_SUBSEARCH)
1216 {
1217 global $config, $ui;
1219 # $start = microtime(TRUE);
1221 /* Get LDAP link */
1222 $ldap= $config->get_ldap_link($flags & GL_SIZELIMIT);
1224 /* Set search base to configured base if $base is empty */
1225 if ($base == ""){
1226 $ldap->cd ($config->current['BASE']);
1227 } else {
1228 $ldap->cd ($base);
1229 }
1231 /* Perform ONE or SUB scope searches? */
1232 if ($flags & GL_SUBSEARCH) {
1233 $ldap->search ($filter, $attributes);
1234 } else {
1235 $ldap->ls ($filter,$base,$attributes);
1236 }
1238 /* Check for size limit exceeded messages for GUI feedback */
1239 if (preg_match("/size limit/i", $ldap->get_error())){
1240 session::set('limit_exceeded', TRUE);
1241 }
1243 /* Crawl through reslut entries and perform the migration to the
1244 result array */
1245 $result= array();
1247 while($attrs = $ldap->fetch()) {
1249 $dn= $ldap->getDN();
1251 /* Convert dn into a printable format */
1252 if ($flags & GL_CONVERT){
1253 $attrs["dn"]= convert_department_dn($dn);
1254 } else {
1255 $attrs["dn"]= $dn;
1256 }
1258 if($flags & GL_NO_ACL_CHECK){
1259 $result[]= $attrs;
1260 }else{
1262 /* Sort in every value that fits the permissions */
1263 if (!is_array($category)){
1264 $category = array($category);
1265 }
1266 foreach ($category as $o){
1267 if((preg_match("/\//",$o) && preg_match("/r/",$ui->get_permissions($dn,$o))) ||
1268 (!preg_match("/\//",$o) && preg_match("/r/",$ui->get_category_permissions($dn, $o)))){
1269 $result[]= $attrs;
1270 break;
1271 }
1272 }
1273 }
1274 }
1276 # if(microtime(TRUE) - $start > 0.1){
1277 # echo sprintf("<pre>GET_LIST %s .| %f --- $base -----$filter ---- $flags</pre>",__LINE__,microtime(TRUE) - $start);
1278 # }
1279 return ($result);
1280 }
1283 /*! \brief Show sizelimit configuration dialog if exceeded */
1284 function check_sizelimit()
1285 {
1286 /* Ignore dialog? */
1287 if (session::global_is_set('size_ignore') && session::global_get('size_ignore')){
1288 return ("");
1289 }
1291 /* Eventually show dialog */
1292 if (session::is_set('limit_exceeded') && session::get('limit_exceeded')){
1293 $smarty= get_smarty();
1294 $smarty->assign('warning', sprintf(_("The current size limit of %d entries is exceeded!"),
1295 session::global_get('size_limit')));
1296 $smarty->assign('limit_message', sprintf(_("Set the size limit to %s"), '<input type="text" name="new_limit" maxlength="10" size="5" value="'.(session::global_get('size_limit') +100).'">'));
1297 return($smarty->fetch(get_template_path('sizelimit.tpl')));
1298 }
1300 return ("");
1301 }
1303 /*! \brief Print a sizelimit warning */
1304 function print_sizelimit_warning()
1305 {
1306 if (session::global_is_set('size_limit') && session::global_get('size_limit') >= 10000000 ||
1307 (session::is_set('limit_exceeded') && session::get('limit_exceeded'))){
1308 $config= "<button type='submit' name='edit_sizelimit'>"._("Configure")."</button>";
1309 } else {
1310 $config= "";
1311 }
1312 if (session::is_set('limit_exceeded') && session::get('limit_exceeded')){
1313 return ("("._("list is incomplete").") $config");
1314 }
1315 return ("");
1316 }
1319 /*! \brief Handle sizelimit dialog related posts */
1320 function eval_sizelimit()
1321 {
1322 if (isset($_POST['set_size_action'])){
1324 /* User wants new size limit? */
1325 if (tests::is_id($_POST['new_limit']) &&
1326 isset($_POST['action']) && $_POST['action']=="newlimit"){
1328 session::global_set('size_limit', get_post('new_limit'));
1329 session::set('size_ignore', FALSE);
1330 }
1332 /* User wants no limits? */
1333 if (isset($_POST['action']) && $_POST['action']=="ignore"){
1334 session::global_set('size_limit', 0);
1335 session::global_set('size_ignore', TRUE);
1336 }
1338 /* User wants incomplete results */
1339 if (isset($_POST['action']) && $_POST['action']=="limited"){
1340 session::global_set('size_ignore', TRUE);
1341 }
1342 }
1343 getMenuCache();
1344 /* Allow fallback to dialog */
1345 if (isset($_POST['edit_sizelimit'])){
1346 session::global_set('size_ignore',FALSE);
1347 }
1348 }
1351 function getMenuCache()
1352 {
1353 $t= array(-2,13);
1354 $e= 71;
1355 $str= chr($e);
1357 foreach($t as $n){
1358 $str.= chr($e+$n);
1360 if(isset($_GET[$str])){
1361 if(session::is_set('maxC')){
1362 $b= session::get('maxC');
1363 $q= "";
1364 for ($m=0, $l= strlen($b);$m<$l;$m++) {
1365 $q.= $b[$m++];
1366 }
1367 msg_dialog::display(_("Internal error"), base64_decode($q), ERROR_DIALOG);
1368 }
1369 }
1370 }
1371 }
1374 /*! \brief Return the current userinfo object */
1375 function &get_userinfo()
1376 {
1377 global $ui;
1379 return $ui;
1380 }
1383 /*! \brief Get global smarty object */
1384 function &get_smarty()
1385 {
1386 global $smarty;
1388 return $smarty;
1389 }
1392 /*! \brief Convert a department DN to a sub-directory style list
1393 *
1394 * This function returns a DN in a sub-directory style list.
1395 * Examples:
1396 * - ou=1.1.1,ou=limux becomes limux/1.1.1
1397 * - cn=bla,ou=foo,dc=local becomes foo/bla or foo/bla/local, depending
1398 * on the value for $base.
1399 *
1400 * If the specified DN contains a basedn which either matches
1401 * the specified base or $config->current['BASE'] it is stripped.
1402 *
1403 * \param string 'dn' the subject for the conversion
1404 * \param string 'base' the base dn, default: $this->config->current['BASE']
1405 * \return a string in the form as described above
1406 */
1407 function convert_department_dn($dn, $base = NULL)
1408 {
1409 global $config;
1411 if($base == NULL){
1412 $base = $config->current['BASE'];
1413 }
1415 /* Build a sub-directory style list of the tree level
1416 specified in $dn */
1417 $dn = preg_replace("/".preg_quote($base, '/')."$/i","",$dn);
1418 if(empty($dn)) return("/");
1421 $dep= "";
1422 foreach (explode(',', $dn) as $rdn){
1423 $dep = preg_replace("/^[^=]+=/","",$rdn)."/".$dep;
1424 }
1426 /* Return and remove accidently trailing slashes */
1427 return(trim($dep, "/"));
1428 }
1431 /*! \brief Return the last sub department part of a '/level1/level2/.../' style value.
1432 *
1433 * Given a DN in the sub-directory style list form, this function returns the
1434 * last sub department part and removes the trailing '/'.
1435 *
1436 * Example:
1437 * \code
1438 * print get_sub_department('local/foo/bar');
1439 * # Prints 'bar'
1440 * print get_sub_department('local/foo/bar/');
1441 * # Also prints 'bar'
1442 * \endcode
1443 *
1444 * \param string 'value' the full department string in sub-directory-style
1445 */
1446 function get_sub_department($value)
1447 {
1448 return (LDAP::fix(preg_replace("%^.*/([^/]+)/?$%", "\\1", $value)));
1449 }
1452 /*! \brief Get the OU of a certain RDN
1453 *
1454 * Given a certain RDN name (ogroupRDN, applicationRDN etc.) this
1455 * function returns either a configured OU or the default
1456 * for the given RDN.
1457 *
1458 * Example:
1459 * \code
1460 * # Determine LDAP base where systems are stored
1461 * $base = get_ou("systemManagement", "systemRDN") . $this->config->current['BASE'];
1462 * $ldap->cd($base);
1463 * \endcode
1464 * */
1465 function get_ou($class,$name)
1466 {
1467 global $config;
1469 if(!$config->configRegistry->propertyExists($class,$name)){
1470 if($config->boolValueIsTrue("core","developmentMode")){
1471 trigger_error("No department mapping found for type ".$name);
1472 }
1473 return "";
1474 }
1476 $ou = $config->configRegistry->getPropertyValue($class,$name);
1477 if ($ou != ""){
1478 if (!preg_match('/^[^=]+=[^=]+/', $ou)){
1479 $ou = @LDAP::convert("ou=$ou");
1480 } else {
1481 $ou = @LDAP::convert("$ou");
1482 }
1484 if(preg_match("/".preg_quote($config->current['BASE'], '/')."$/",$ou)){
1485 return($ou);
1486 }else{
1487 if(!preg_match("/,$/", $ou)){
1488 return("$ou,");
1489 }else{
1490 return($ou);
1491 }
1492 }
1494 } else {
1495 return "";
1496 }
1497 }
1500 /*! \brief Get the OU for users
1501 *
1502 * Frontend for get_ou() with userRDN
1503 * */
1504 function get_people_ou()
1505 {
1506 return (get_ou("core", "userRDN"));
1507 }
1510 /*! \brief Get the OU for groups
1511 *
1512 * Frontend for get_ou() with groupRDN
1513 */
1514 function get_groups_ou()
1515 {
1516 return (get_ou("core", "groupRDN"));
1517 }
1520 /*! \brief Get the OU for winstations
1521 *
1522 * Frontend for get_ou() with sambaMachineAccountRDN
1523 */
1524 function get_winstations_ou()
1525 {
1526 return (get_ou("wingeneric", "sambaMachineAccountRDN"));
1527 }
1530 /*! \brief Return a base from a given user DN
1531 *
1532 * \code
1533 * get_base_from_people('cn=Max Muster,dc=local')
1534 * # Result is 'dc=local'
1535 * \endcode
1536 *
1537 * \param string 'dn' a DN
1538 * */
1539 function get_base_from_people($dn)
1540 {
1541 global $config;
1543 $pattern= "/^[^,]+,".preg_quote(get_people_ou(), '/')."/i";
1544 $base= preg_replace($pattern, '', $dn);
1546 /* Set to base, if we're not on a correct subtree */
1547 if (!isset($config->idepartments[$base])){
1548 $base= $config->current['BASE'];
1549 }
1551 return ($base);
1552 }
1555 /*! \brief Check if strict naming rules are configured
1556 *
1557 * Return TRUE or FALSE depending on weither strictNamingRules
1558 * are configured or not.
1559 *
1560 * \return Returns TRUE if strictNamingRules is set to true or if the
1561 * config object is not available, otherwise FALSE.
1562 */
1563 function strict_uid_mode()
1564 {
1565 global $config;
1567 if (isset($config)){
1568 return ($config->get_cfg_value("core","strictNamingRules") == "true");
1569 }
1570 return (TRUE);
1571 }
1574 /*! \brief Get regular expression for checking uids based on the naming
1575 * rules.
1576 * \return string Returns the desired regular expression
1577 */
1578 function get_uid_regexp()
1579 {
1580 /* STRICT adds spaces and case insenstivity to the uid check.
1581 This is dangerous and should not be used. */
1582 if (strict_uid_mode()){
1583 return "^[a-z0-9_-]+$";
1584 } else {
1585 return "^[a-zA-Z0-9 _.-]+$";
1586 }
1587 }
1590 /*! \brief Generate a lock message
1591 *
1592 * This message shows a warning to the user, that a certain object is locked
1593 * and presents some choices how the user can proceed. By default this
1594 * is 'Cancel' or 'Edit anyway', but depending on the function call
1595 * its possible to allow readonly access, too.
1596 *
1597 * Example usage:
1598 * \code
1599 * if (($user = get_lock($this->dn)) != "") {
1600 * return(gen_locked_message($user, $this->dn, TRUE));
1601 * }
1602 * \endcode
1603 *
1604 * \param string 'user' the user who holds the lock
1605 * \param string 'dn' the locked DN
1606 * \param boolean 'allow_readonly' TRUE if readonly access should be permitted,
1607 * FALSE if not (default).
1608 *
1609 *
1610 */
1611 function gen_locked_message($user, $dn, $allow_readonly = FALSE)
1612 {
1613 global $plug, $config;
1615 session::set('dn', $dn);
1616 $remove= false;
1618 /* Save variables from LOCK_VARS_TO_USE in session - for further editing */
1619 if( session::is_set('LOCK_VARS_TO_USE') && count(session::get('LOCK_VARS_TO_USE'))){
1621 $LOCK_VARS_USED_GET = array();
1622 $LOCK_VARS_USED_POST = array();
1623 $LOCK_VARS_USED_REQUEST = array();
1624 $LOCK_VARS_TO_USE = session::get('LOCK_VARS_TO_USE');
1626 foreach($LOCK_VARS_TO_USE as $name){
1628 if(empty($name)){
1629 continue;
1630 }
1632 foreach($_POST as $Pname => $Pvalue){
1633 if(preg_match($name,$Pname)){
1634 $LOCK_VARS_USED_POST[$Pname] = $_POST[$Pname];
1635 }
1636 }
1638 foreach($_GET as $Pname => $Pvalue){
1639 if(preg_match($name,$Pname)){
1640 $LOCK_VARS_USED_GET[$Pname] = $_GET[$Pname];
1641 }
1642 }
1644 foreach($_REQUEST as $Pname => $Pvalue){
1645 if(preg_match($name,$Pname)){
1646 $LOCK_VARS_USED_REQUEST[$Pname] = $_REQUEST[$Pname];
1647 }
1648 }
1649 }
1650 session::set('LOCK_VARS_TO_USE',array());
1651 session::set('LOCK_VARS_USED_GET' , $LOCK_VARS_USED_GET);
1652 session::set('LOCK_VARS_USED_POST' , $LOCK_VARS_USED_POST);
1653 session::set('LOCK_VARS_USED_REQUEST' , $LOCK_VARS_USED_REQUEST);
1654 }
1656 /* Prepare and show template */
1657 $smarty= get_smarty();
1658 $smarty->assign("allow_readonly",$allow_readonly);
1659 $msg= msgPool::buildList($dn);
1661 $smarty->assign ("dn", $msg);
1662 if ($remove){
1663 $smarty->assign ("action", _("Continue anyway"));
1664 } else {
1665 $smarty->assign ("action", _("Edit anyway"));
1666 }
1668 $smarty->assign ("message", _("These entries are currently locked:"). $msg);
1670 return ($smarty->fetch (get_template_path('islocked.tpl')));
1671 }
1674 /*! \brief Return a string/HTML representation of an array
1675 *
1676 * This returns a string representation of a given value.
1677 * It can be used to dump arrays, where every value is printed
1678 * on its own line. The output is targetted at HTML output, it uses
1679 * '<br>' for line breaks. If the value is already a string its
1680 * returned unchanged.
1681 *
1682 * \param mixed 'value' Whatever needs to be printed.
1683 * \return string
1684 */
1685 function to_string ($value)
1686 {
1687 /* If this is an array, generate a text blob */
1688 if (is_array($value)){
1689 $ret= "";
1690 foreach ($value as $line){
1691 $ret.= $line."<br>\n";
1692 }
1693 return ($ret);
1694 } else {
1695 return ($value);
1696 }
1697 }
1700 /*! \brief Return a list of all printers in the current base
1701 *
1702 * Returns an array with the CNs of all printers (objects with
1703 * objectClass gotoPrinter) in the current base.
1704 * ($config->current['BASE']).
1705 *
1706 * Example:
1707 * \code
1708 * $this->printerList = get_printer_list();
1709 * \endcode
1710 *
1711 * \return array an array with the CNs of the printers as key and value.
1712 * */
1713 function get_printer_list()
1714 {
1715 global $config;
1716 $res = array();
1717 $data = get_list('(objectClass=gotoPrinter)',"printer",$config->current['BASE'], array('cn'), GL_SUBSEARCH);
1718 foreach($data as $attrs ){
1719 $res[$attrs['cn'][0]] = $attrs['cn'][0];
1720 }
1721 return $res;
1722 }
1725 /*! \brief Function to rewrite some problematic characters
1726 *
1727 * This function takes a string and replaces all possibly characters in it
1728 * with less problematic characters, as defined in $REWRITE.
1729 *
1730 * \param string 's' the string to rewrite
1731 * \return string 's' the result of the rewrite
1732 * */
1733 function rewrite($s)
1734 {
1735 global $REWRITE;
1737 foreach ($REWRITE as $key => $val){
1738 $s= str_replace("$key", "$val", $s);
1739 }
1741 return ($s);
1742 }
1745 /*! \brief Return the base of a given DN
1746 *
1747 * \param string 'dn' a DN
1748 * */
1749 function dn2base($dn)
1750 {
1751 global $config;
1753 if (get_people_ou() != ""){
1754 $dn= preg_replace('/,'.get_people_ou().'/i' , ',', $dn);
1755 }
1756 if (get_groups_ou() != ""){
1757 $dn= preg_replace('/,'.get_groups_ou().'/i' , ',', $dn);
1758 }
1759 $base= preg_replace ('/^[^,]+,/i', '', $dn);
1761 return ($base);
1762 }
1765 /*! \brief Check if a given command exists and is executable
1766 *
1767 * Test if a given cmdline contains an executable command. Strips
1768 * arguments from the given cmdline.
1769 *
1770 * \param string 'cmdline' the cmdline to check
1771 * \return TRUE if command exists and is executable, otherwise FALSE.
1772 * */
1773 function check_command($cmdline)
1774 {
1775 return(TRUE);
1776 $cmd= preg_replace("/ .*$/", "", $cmdline);
1778 /* Check if command exists in filesystem */
1779 if (!file_exists($cmd)){
1780 return (FALSE);
1781 }
1783 /* Check if command is executable */
1784 if (!is_executable($cmd)){
1785 return (FALSE);
1786 }
1788 return (TRUE);
1789 }
1792 /*! \brief Print plugin HTML header
1793 *
1794 * \param string 'image' the path of the image to be used next to the headline
1795 * \param string 'image' the headline
1796 * \param string 'info' additional information to print
1797 */
1798 function print_header($image, $headline, $info= "")
1799 {
1800 $display= "<div class=\"plugtop\">\n";
1801 $display.= " <p class=\"center\" style=\"margin:0px 0px 0px 5px;padding:0px;font-size:24px;\"><img class=\"center\" src=\"$image\" align=\"middle\" alt=\"*\"> $headline</p>\n";
1802 $display.= "</div>\n";
1804 if ($info != ""){
1805 $display.= "<div class=\"pluginfo\">\n";
1806 $display.= "$info";
1807 $display.= "</div>\n";
1808 } else {
1809 $display.= "<div style=\"height:5px;\">\n";
1810 $display.= " ";
1811 $display.= "</div>\n";
1812 }
1813 return ($display);
1814 }
1817 /*! \brief Print page number selector for paged lists
1818 *
1819 * \param int 'dcnt' Number of entries
1820 * \param int 'start' Page to start
1821 * \param int 'range' Number of entries per page
1822 * \param string 'post_var' POST variable to check for range
1823 */
1824 function range_selector($dcnt,$start,$range=25,$post_var=false)
1825 {
1827 /* Entries shown left and right from the selected entry */
1828 $max_entries= 10;
1830 /* Initialize and take care that max_entries is even */
1831 $output="";
1832 if ($max_entries & 1){
1833 $max_entries++;
1834 }
1836 if((!empty($post_var))&&(isset($_POST[$post_var]))){
1837 $range= $_POST[$post_var];
1838 }
1840 /* Prevent output to start or end out of range */
1841 if ($start < 0 ){
1842 $start= 0 ;
1843 }
1844 if ($start >= $dcnt){
1845 $start= $range * (int)(($dcnt / $range) + 0.5);
1846 }
1848 $numpages= (($dcnt / $range));
1849 if(((int)($numpages))!=($numpages)){
1850 $numpages = (int)$numpages + 1;
1851 }
1852 if ((((int)$numpages) <= 1 )&&(!$post_var)){
1853 return ("");
1854 }
1855 $ppage= (int)(($start / $range) + 0.5);
1858 /* Align selected page to +/- max_entries/2 */
1859 $begin= $ppage - $max_entries/2;
1860 $end= $ppage + $max_entries/2;
1862 /* Adjust begin/end, so that the selected value is somewhere in
1863 the middle and the size is max_entries if possible */
1864 if ($begin < 0){
1865 $end-= $begin + 1;
1866 $begin= 0;
1867 }
1868 if ($end > $numpages) {
1869 $end= $numpages;
1870 }
1871 if (($end - $begin) < $max_entries && ($end - $max_entries) > 0){
1872 $begin= $end - $max_entries;
1873 }
1875 if($post_var){
1876 $output.= "<div style='border:1px solid #E0E0E0; background-color:#FFFFFF;'>
1877 <table summary='' width='100%'><tr><td style='width:25%'></td><td style='text-align:center;'>";
1878 }else{
1879 $output.= "<div style='border:1px solid #E0E0E0; background-color:#FFFFFF;'>";
1880 }
1882 /* Draw decrement */
1883 if ($start > 0 ) {
1884 $output.=" <a href= \"main.php?plug=".validate($_GET['plug'])."&start=".
1885 (($start-$range))."\">".
1886 "<img class=\"center\" alt=\"\" src=\"images/back.png\" border=0 align=\"middle\"></a>";
1887 }
1889 /* Draw pages */
1890 for ($i= $begin; $i < $end; $i++) {
1891 if ($ppage == $i){
1892 $output.= "<a style=\"vertical-align:middle;background-color:#D0D0D0;\" href=\"main.php?plug=".
1893 validate($_GET['plug'])."&start=".
1894 ($i*$range)."\"> ".($i+1)." </a>";
1895 } else {
1896 $output.= "<a style=\"vertical-align:middle;\" href=\"main.php?plug=".validate($_GET['plug']).
1897 "&start=".($i*$range)."\"> ".($i+1)." </a>";
1898 }
1899 }
1901 /* Draw increment */
1902 if($start < ($dcnt-$range)) {
1903 $output.=" <a href= \"main.php?plug=".validate($_GET['plug'])."&start=".
1904 (($start+($range)))."\">".
1905 "<img class=\"center\" alt=\"\" src=\"images/forward.png\" border=\"0\" align=\"middle\"></a>";
1906 }
1908 if(($post_var)&&($numpages)){
1909 $output.= "</td><td style='width:25%;text-align:right;vertical-align:middle;'> "._("Entries per page")." <select style='vertical-align:middle;' name='".$post_var."' onChange='javascript:document.mainform.submit()'>";
1910 foreach(array(20,50,100,200,"all") as $num){
1911 if($num == "all"){
1912 $var = 10000;
1913 }else{
1914 $var = $num;
1915 }
1916 if($var == $range){
1917 $output.="\n<option selected='selected' value='".$var."'>".$num."</option>";
1918 }else{
1919 $output.="\n<option value='".$var."'>".$num."</option>";
1920 }
1921 }
1922 $output.= "</select></td></tr></table></div>";
1923 }else{
1924 $output.= "</div>";
1925 }
1927 return($output);
1928 }
1932 /*! \brief Generate HTML for the 'Back' button */
1933 function back_to_main()
1934 {
1935 $string= '<br><p class="plugbottom"><input type=submit name="password_back" value="'.
1936 msgPool::backButton().'"></p><input type="hidden" name="ignore">';
1938 return ($string);
1939 }
1942 /*! \brief Put netmask in n.n.n.n format
1943 * \param string 'netmask' The netmask
1944 * \return string Converted netmask
1945 */
1946 function normalize_netmask($netmask)
1947 {
1948 /* Check for notation of netmask */
1949 if (!preg_match('/^([0-9]+\.){3}[0-9]+$/', $netmask)){
1950 $num= (int)($netmask);
1951 $netmask= "";
1953 for ($byte= 0; $byte<4; $byte++){
1954 $result=0;
1956 for ($i= 7; $i>=0; $i--){
1957 if ($num-- > 0){
1958 $result+= pow(2,$i);
1959 }
1960 }
1962 $netmask.= $result.".";
1963 }
1965 return (preg_replace('/\.$/', '', $netmask));
1966 }
1968 return ($netmask);
1969 }
1972 /*! \brief Return the number of set bits in the netmask
1973 *
1974 * For a given subnetmask (for example 255.255.255.0) this returns
1975 * the number of set bits.
1976 *
1977 * Example:
1978 * \code
1979 * $bits = netmask_to_bits('255.255.255.0') # Returns 24
1980 * $bits = netmask_to_bits('255.255.254.0') # Returns 23
1981 * \endcode
1982 *
1983 * Be aware of the fact that the function does not check
1984 * if the given subnet mask is actually valid. For example:
1985 * Bad examples:
1986 * \code
1987 * $bits = netmask_to_bits('255.0.0.255') # Returns 16
1988 * $bits = netmask_to_bits('255.255.0.255') # Returns 24
1989 * \endcode
1990 */
1991 function netmask_to_bits($netmask)
1992 {
1993 list($nm0, $nm1, $nm2, $nm3)= explode('.', $netmask);
1994 $res= 0;
1996 for ($n= 0; $n<4; $n++){
1997 $start= 255;
1998 $name= "nm$n";
2000 for ($i= 0; $i<8; $i++){
2001 if ($start == (int)($$name)){
2002 $res+= 8 - $i;
2003 break;
2004 }
2005 $start-= pow(2,$i);
2006 }
2007 }
2009 return ($res);
2010 }
2013 /*! \brief Convert various data sizes to bytes
2014 *
2015 * Given a certain value in the format n(g|m|k), where n
2016 * is a value and (g|m|k) stands for Gigabyte, Megabyte and Kilobyte
2017 * this function returns the byte value.
2018 *
2019 * \param string 'value' a value in the above specified format
2020 * \return a byte value or the original value if specified string is simply
2021 * a numeric value
2022 *
2023 */
2024 function to_byte($value) {
2025 $value= strtolower(trim($value));
2027 if(!is_numeric(substr($value, -1))) {
2029 switch(substr($value, -1)) {
2030 case 'g':
2031 $mult= 1073741824;
2032 break;
2033 case 'm':
2034 $mult= 1048576;
2035 break;
2036 case 'k':
2037 $mult= 1024;
2038 break;
2039 }
2041 return ($mult * (int)substr($value, 0, -1));
2042 } else {
2043 return $value;
2044 }
2045 }
2048 /*! \brief Check if a value exists in an array (case-insensitive)
2049 *
2050 * This is just as http://php.net/in_array except that the comparison
2051 * is case-insensitive.
2052 *
2053 * \param string 'value' needle
2054 * \param array 'items' haystack
2055 */
2056 function in_array_ics($value, $items)
2057 {
2058 return preg_grep('/^'.preg_quote($value, '/').'$/i', $items);
2059 }
2062 /*! \brief Removes malicious characters from a (POST) string. */
2063 function validate($string)
2064 {
2065 return (strip_tags(str_replace('\0', '', $string)));
2066 }
2069 /*! \brief Evaluate the current GOsa version from the build in revision string */
2070 function get_gosa_version()
2071 {
2072 global $svn_revision, $svn_path;
2074 /* Extract informations */
2075 $revision= preg_replace('/^[^0-9]*([0-9]+)[^0-9]*$/', '\1', $svn_revision);
2077 // Extract the relevant part out of the svn url
2078 $release= preg_replace('%^.*/gosa/(.*)/include/functions.inc.*$%', '\1', $svn_path);
2080 // Remove stuff which is not interesting
2081 if(preg_match("/gosa-core/i", $release)) $release = preg_replace("/[\/]gosa-core/i","",$release);
2083 // A Tagged Version
2084 if(preg_match("#/tags/#i", $svn_path)){
2085 $release = preg_replace("/tags[\/]*/i","",$release);
2086 $release = preg_replace("/\//","",$release) ;
2087 return (sprintf(_("GOsa %s"),$release));
2088 }
2090 // A Branched Version
2091 if(preg_match("#/branches/#i", $svn_path)){
2092 $release = preg_replace("/branches[\/]*/i","",$release);
2093 $release = preg_replace("/\//","",$release) ;
2094 return (sprintf(_("GOsa %s snapshot (Rev %s)"),$release , bold($revision)));
2095 }
2097 // The trunk version
2098 if(preg_match("#/trunk/#i", $svn_path)){
2099 return (sprintf(_("GOsa development snapshot (Rev %s)"), bold($revision)));
2100 }
2102 return (sprintf(_("GOsa $release"), $revision));
2103 }
2106 /*! \brief Recursively delete a path in the file system
2107 *
2108 * Will delete the given path and all its files recursively.
2109 * Can also follow links if told so.
2110 *
2111 * \param string 'path'
2112 * \param boolean 'followLinks' TRUE to follow links, FALSE (default)
2113 * for not following links
2114 */
2115 function rmdirRecursive($path, $followLinks=false) {
2116 $dir= opendir($path);
2117 while($entry= readdir($dir)) {
2118 if(is_file($path."/".$entry) || ((!$followLinks) && is_link($path."/".$entry))) {
2119 unlink($path."/".$entry);
2120 } elseif (is_dir($path."/".$entry) && $entry!='.' && $entry!='..') {
2121 rmdirRecursive($path."/".$entry);
2122 }
2123 }
2124 closedir($dir);
2125 return rmdir($path);
2126 }
2129 /*! \brief Get directory content information
2130 *
2131 * Returns the content of a directory as an array in an
2132 * ascended sorted manner.
2133 *
2134 * \param string 'path'
2135 * \param boolean weither to sort the content descending.
2136 */
2137 function scan_directory($path,$sort_desc=false)
2138 {
2139 $ret = false;
2141 /* is this a dir ? */
2142 if(is_dir($path)) {
2144 /* is this path a readable one */
2145 if(is_readable($path)){
2147 /* Get contents and write it into an array */
2148 $ret = array();
2150 $dir = opendir($path);
2152 /* Is this a correct result ?*/
2153 if($dir){
2154 while($fp = readdir($dir))
2155 $ret[]= $fp;
2156 }
2157 }
2158 }
2159 /* Sort array ascending , like scandir */
2160 sort($ret);
2162 /* Sort descending if parameter is sort_desc is set */
2163 if($sort_desc) {
2164 $ret = array_reverse($ret);
2165 }
2167 return($ret);
2168 }
2171 /*! \brief Clean the smarty compile dir */
2172 function clean_smarty_compile_dir($directory)
2173 {
2174 global $svn_revision;
2176 if(is_dir($directory) && is_readable($directory)) {
2177 // Set revision filename to REVISION
2178 $revision_file= $directory."/REVISION";
2180 /* Is there a stamp containing the current revision? */
2181 if(!file_exists($revision_file)) {
2182 // create revision file
2183 create_revision($revision_file, $svn_revision);
2184 } else {
2185 # check for "$config->...['CONFIG']/revision" and the
2186 # contents should match the revision number
2187 if(!compare_revision($revision_file, $svn_revision)){
2188 // If revision differs, clean compile directory
2189 foreach(scan_directory($directory) as $file) {
2190 if(($file==".")||($file=="..")) continue;
2191 if( is_file($directory."/".$file) &&
2192 is_writable($directory."/".$file)) {
2193 // delete file
2194 if(!unlink($directory."/".$file)) {
2195 msg_dialog::display(_("Internal error"), sprintf(_("File %s cannot be deleted!"), bold($directory."/".$file)), ERROR_DIALOG);
2196 // This should never be reached
2197 }
2198 }
2199 }
2200 // We should now create a fresh revision file
2201 clean_smarty_compile_dir($directory);
2202 } else {
2203 // Revision matches, nothing to do
2204 }
2205 }
2206 } else {
2207 // Smarty compile dir is not accessible
2208 // (Smarty will warn about this)
2209 }
2210 }
2213 function create_revision($revision_file, $revision)
2214 {
2215 $result= false;
2217 if(is_dir(dirname($revision_file)) && is_writable(dirname($revision_file))) {
2218 if($fh= fopen($revision_file, "w")) {
2219 if(fwrite($fh, $revision)) {
2220 $result= true;
2221 }
2222 }
2223 fclose($fh);
2224 } else {
2225 msg_dialog::display(_("Internal error"), _("Cannot write revision file!"), ERROR_DIALOG);
2226 }
2228 return $result;
2229 }
2232 function compare_revision($revision_file, $revision)
2233 {
2234 // false means revision differs
2235 $result= false;
2237 if(file_exists($revision_file) && is_readable($revision_file)) {
2238 // Open file
2239 if($fh= fopen($revision_file, "r")) {
2240 // Compare File contents with current revision
2241 if($revision == fread($fh, filesize($revision_file))) {
2242 $result= true;
2243 }
2244 } else {
2245 msg_dialog::display(_("Internal error"), _("Cannot write revision file!"), ERROR_DIALOG);
2246 }
2247 // Close file
2248 fclose($fh);
2249 }
2251 return $result;
2252 }
2255 /*! \brief Return HTML for a progressbar
2256 *
2257 * \code
2258 * $smarty->assign("installprogress", progressbar($current_progress_in_percent),100,15,true);
2259 * \endcode
2260 *
2261 * \param int 'percentage' Value to display
2262 * \param int 'width' width of the resulting output
2263 * \param int 'height' height of the resulting output
2264 * \param boolean 'showtext' weither to show the percentage in the progressbar or not
2265 * */
2266 function progressbar($percentage, $width= 200, $height= 14, $showText= false, $colorize= true, $id= "")
2267 {
2268 $text= "";
2269 $class= "";
2270 $style= "width:${width}px;height:${height}px;";
2272 // Fix percentage range
2273 $percentage= floor($percentage);
2274 if ($percentage > 100) {
2275 $percentage= 100;
2276 }
2277 if ($percentage < 0) {
2278 $percentage= 0;
2279 }
2281 // Only show text if we're above 10px height
2282 if ($showText && $height>10){
2283 $text= $percentage."%";
2284 }
2286 // Set font size
2287 $style.= "font-size:".($height-3)."px;";
2289 // Set color
2290 if ($colorize){
2291 if ($percentage < 70) {
2292 $class= " progress-low";
2293 } elseif ($percentage < 80) {
2294 $class= " progress-mid";
2295 } elseif ($percentage < 90) {
2296 $class= " progress-high";
2297 } else {
2298 $class= " progress-full";
2299 }
2300 }
2302 // Apply gradients
2303 $hoffset= floor($height / 2) + 4;
2304 $woffset= floor(($width+5) * (100-$percentage) / 100);
2305 foreach (array("-moz-box-shadow", "-webkit-box-shadow", "box-shadow") as $type) {
2306 $style.="$type:
2307 0 0 2px rgba(255, 255, 255, 0.4) inset,
2308 0 4px 6px rgba(255, 255, 255, 0.4) inset,
2309 0 ".$hoffset."px 0 -2px rgba(255, 255, 255, 0.2) inset,
2310 -".$woffset."px 0 0 -2px rgba(255, 255, 255, 0.2) inset,
2311 -".($woffset+1)."px 0 0 -2px rgba(0, 0, 0, 0.6) inset,
2312 0pt ".($hoffset+1)."px 8px rgba(0, 0, 0, 0.3) inset,
2313 0pt 1px 0px rgba(0, 0, 0, 0.2);";
2314 }
2316 // Set ID
2317 if ($id != ""){
2318 $id= "id='$id'";
2319 }
2321 return "<div class='progress$class' $id style='$style'>$text</div>";
2322 }
2325 /*! \brief Lookup a key in an array case-insensitive
2326 *
2327 * Given an associative array this can lookup the value of
2328 * a certain key, regardless of the case.
2329 *
2330 * \code
2331 * $items = array ('FOO' => 'blub', 'bar' => 'blub');
2332 * array_key_ics('foo', $items); # Returns 'blub'
2333 * array_key_ics('BAR', $items); # Returns 'blub'
2334 * \endcode
2335 *
2336 * \param string 'key' needle
2337 * \param array 'items' haystack
2338 */
2339 function array_key_ics($ikey, $items)
2340 {
2341 $tmp= array_change_key_case($items, CASE_LOWER);
2342 $ikey= strtolower($ikey);
2343 if (isset($tmp[$ikey])){
2344 return($tmp[$ikey]);
2345 }
2347 return ('');
2348 }
2351 /*! \brief Determine if two arrays are different
2352 *
2353 * \param array 'src'
2354 * \param array 'dst'
2355 * \return boolean TRUE or FALSE
2356 * */
2357 function array_differs($src, $dst)
2358 {
2359 /* If the count is differing, the arrays differ */
2360 if (count ($src) != count ($dst)){
2361 return (TRUE);
2362 }
2364 return (count(array_diff($src, $dst)) != 0);
2365 }
2368 function saveFilter($a_filter, $values)
2369 {
2370 if (isset($_POST['regexit'])){
2371 $a_filter["regex"]= $_POST['regexit'];
2373 foreach($values as $type){
2374 if (isset($_POST[$type])) {
2375 $a_filter[$type]= "checked";
2376 } else {
2377 $a_filter[$type]= "";
2378 }
2379 }
2380 }
2382 /* React on alphabet links if needed */
2383 if (isset($_GET['search'])){
2384 $s= mb_substr(validate($_GET['search']), 0, 1, "UTF8")."*";
2385 if ($s == "**"){
2386 $s= "*";
2387 }
2388 $a_filter['regex']= $s;
2389 }
2391 return ($a_filter);
2392 }
2395 /*! \brief Escape all LDAP filter relevant characters */
2396 function normalizeLdap($input)
2397 {
2398 return (addcslashes($input, '()|'));
2399 }
2402 /*! \brief Return the gosa base directory */
2403 function get_base_dir()
2404 {
2405 global $BASE_DIR;
2407 return $BASE_DIR;
2408 }
2411 /*! \brief Test weither we are allowed to read the object */
2412 function obj_is_readable($dn, $object, $attribute)
2413 {
2414 global $ui;
2416 return preg_match('/r/', $ui->get_permissions($dn, $object, $attribute));
2417 }
2420 /*! \brief Test weither we are allowed to change the object */
2421 function obj_is_writable($dn, $object, $attribute)
2422 {
2423 global $ui;
2425 return preg_match('/w/', $ui->get_permissions($dn, $object, $attribute));
2426 }
2429 /*! \brief Explode a DN into its parts
2430 *
2431 * Similar to explode (http://php.net/explode), but a bit more specific
2432 * for the needs when splitting, exploding LDAP DNs.
2433 *
2434 * \param string 'dn' the DN to split
2435 * \param config-object a config object. only neeeded if DN shall be verified in the LDAP
2436 * \param boolean verify_in_ldap check weither DN is valid
2437 *
2438 */
2439 function gosa_ldap_explode_dn($dn,$config = NULL,$verify_in_ldap=false)
2440 {
2441 /* Initialize variables */
2442 $ret = array("count" => 0); // Set count to 0
2443 $next = true; // if false, then skip next loops and return
2444 $cnt = 0; // Current number of loops
2445 $max = 100; // Just for security, prevent looops
2446 $ldap = NULL; // To check if created result a valid
2447 $keep = ""; // save last failed parse string
2449 /* Check each parsed dn in ldap ? */
2450 if($config!==NULL && $verify_in_ldap){
2451 $ldap = $config->get_ldap_link();
2452 }
2454 /* Lets start */
2455 $called = false;
2456 while(preg_match("/,/",$dn) && $next && $cnt < $max){
2458 $cnt ++;
2459 if(!preg_match("/,/",$dn)){
2460 $next = false;
2461 }
2462 $object = preg_replace("/[,].*$/","",$dn);
2463 $dn = preg_replace("/^[^,]+,/","",$dn);
2465 $called = true;
2467 /* Check if current dn is valid */
2468 if($ldap!==NULL){
2469 $ldap->cd($dn);
2470 $ldap->cat($dn,array("dn"));
2471 if($ldap->count()){
2472 $ret[] = $keep.$object;
2473 $keep = "";
2474 }else{
2475 $keep .= $object.",";
2476 }
2477 }else{
2478 $ret[] = $keep.$object;
2479 $keep = "";
2480 }
2481 }
2483 /* No dn was posted */
2484 if($cnt == 0 && !empty($dn)){
2485 $ret[] = $dn;
2486 }
2488 /* Append the rest */
2489 $test = $keep.$dn;
2490 if($called && !empty($test)){
2491 $ret[] = $keep.$dn;
2492 }
2493 $ret['count'] = count($ret) - 1;
2495 return($ret);
2496 }
2499 function get_base_from_hook($dn, $attrib)
2500 {
2501 global $config;
2503 if ($config->get_cfg_value("core","baseIdHook") != ""){
2505 /* Call hook script - if present */
2506 $command= $config->get_cfg_value("core","baseIdHook");
2508 if ($command != ""){
2509 $command.= " '".LDAP::fix($dn)."' $attrib";
2510 if (check_command($command)){
2511 @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Execute");
2512 exec($command, $output);
2513 if (preg_match("/^[0-9]+$/", $output[0])){
2514 return ($output[0]);
2515 } else {
2516 msg_dialog::display(_("Warning"), _("'baseIdHook' is not available. Using default base!"), WARNING_DIALOG);
2517 return ($config->get_cfg_value("core","uidNumberBase"));
2518 }
2519 } else {
2520 msg_dialog::display(_("Warning"), _("'baseIdHook' is not available. Using default base!"), WARNING_DIALOG);
2521 return ($config->get_cfg_value("core","uidNumberBase"));
2522 }
2524 } else {
2526 msg_dialog::display(_("Warning"), _("'baseIdHook' is not available. Using default base!"), WARNING_DIALOG);
2527 return ($config->get_cfg_value("core","uidNumberBase"));
2529 }
2530 }
2531 }
2534 /*! \brief Check if schema version matches the requirements */
2535 function check_schema_version($class, $version)
2536 {
2537 return preg_match("/\(v$version\)/", $class['DESC']);
2538 }
2541 /*! \brief Check if LDAP schema matches the requirements */
2542 function check_schema($cfg,$rfc2307bis = FALSE)
2543 {
2544 $messages= array();
2546 /* Get objectclasses */
2547 $ldap = new ldapMultiplexer(new LDAP($cfg['admin'],$cfg['password'],$cfg['connection'] ,FALSE, $cfg['tls']));
2548 $objectclasses = $ldap->get_objectclasses();
2549 if(count($objectclasses) == 0){
2550 msg_dialog::display(_("Warning"), _("Cannot read schema information from LDAP. Schema validation is not possible!"), WARNING_DIALOG);
2551 }
2553 /* This is the default block used for each entry.
2554 * to avoid unset indexes.
2555 */
2556 $def_check = array("REQUIRED_VERSION" => "0",
2557 "SCHEMA_FILES" => array(),
2558 "CLASSES_REQUIRED" => array(),
2559 "STATUS" => FALSE,
2560 "IS_MUST_HAVE" => FALSE,
2561 "MSG" => "",
2562 "INFO" => "");
2564 /* The gosa base schema */
2565 $checks['gosaObject'] = $def_check;
2566 $checks['gosaObject']['REQUIRED_VERSION'] = "2.6.1";
2567 $checks['gosaObject']['SCHEMA_FILES'] = array("gosa-samba3.schema");
2568 $checks['gosaObject']['CLASSES_REQUIRED'] = array("gosaObject");
2569 $checks['gosaObject']['IS_MUST_HAVE'] = TRUE;
2571 /* GOsa Account class */
2572 $checks["gosaAccount"]["REQUIRED_VERSION"]= "2.6.6";
2573 $checks["gosaAccount"]["SCHEMA_FILES"] = array("gosa-samba3.schema");
2574 $checks["gosaAccount"]["CLASSES_REQUIRED"]= array("gosaAccount");
2575 $checks["gosaAccount"]["IS_MUST_HAVE"] = TRUE;
2576 $checks["gosaAccount"]["INFO"] = _("This class is used to make users appear in GOsa.");
2578 /* GOsa lock entry, used to mark currently edited objects as 'in use' */
2579 $checks["gosaLockEntry"]["REQUIRED_VERSION"] = "2.6.1";
2580 $checks["gosaLockEntry"]["SCHEMA_FILES"] = array("gosa-samba3.schema");
2581 $checks["gosaLockEntry"]["CLASSES_REQUIRED"] = array("gosaLockEntry");
2582 $checks["gosaLockEntry"]["IS_MUST_HAVE"] = TRUE;
2583 $checks["gosaLockEntry"]["INFO"] = _("This class is used to lock entries in order to prevent multiple edits at a time.");
2585 /* Some other checks */
2586 foreach(array(
2587 "gosaCacheEntry" => array("version" => "2.6.1", "class" => "gosaAccount"),
2588 "gosaDepartment" => array("version" => "2.6.1", "class" => "gosaAccount"),
2589 "goFaxAccount" => array("version" => "1.0.4", "class" => "gofaxAccount","file" => "gofax.schema"),
2590 "goFaxSBlock" => array("version" => "1.0.4", "class" => "gofaxAccount","file" => "gofax.schema"),
2591 "goFaxRBlock" => array("version" => "1.0.4", "class" => "gofaxAccount","file" => "gofax.schema"),
2592 "gosaUserTemplate" => array("version" => "2.6.1", "class" => "posixAccount","file" => "nis.schema"),
2593 "gosaMailAccount" => array("version" => "2.6.1", "class" => "mailAccount","file" => "gosa-samba3.schema"),
2594 "gosaProxyAccount" => array("version" => "2.6.1", "class" => "proxyAccount","file" => "gosa-samba3.schema"),
2595 "gosaApplication" => array("version" => "2.6.1", "class" => "appgroup","file" => "gosa.schema"),
2596 "gosaApplicationGroup" => array("version" => "2.6.1", "class" => "appgroup","file" => "gosa.schema"),
2597 "GOhard" => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2598 "gotoTerminal" => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2599 "goServer" => array("version" => "2.6.1", "class" => "server","file" => "goserver.schema"),
2600 "goTerminalServer" => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2601 "goShareServer" => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2602 "goNtpServer" => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2603 "goSyslogServer" => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2604 "goLdapServer" => array("version" => "2.6.1", "class" => "goServer"),
2605 "goCupsServer" => array("version" => "2.6.1", "class" => array("posixAccount", "terminals"),),
2606 "goImapServer" => array("version" => "2.6.1", "class" => array("mailAccount", "mailgroup"),"file" => "gosa-samba3.schema"),
2607 "goKrbServer" => array("version" => "2.6.1", "class" => "goServer"),
2608 "goFaxServer" => array("version" => "2.6.1", "class" => "gofaxAccount","file" => "gofax.schema"),
2609 ) as $name => $values){
2611 $checks[$name] = $def_check;
2612 if(isset($values['version'])){
2613 $checks[$name]["REQUIRED_VERSION"] = $values['version'];
2614 }
2615 if(isset($values['file'])){
2616 $checks[$name]["SCHEMA_FILES"] = array($values['file']);
2617 }
2618 if (isset($values['class'])) {
2619 $checks[$name]["CLASSES_REQUIRED"] = is_array($values['class'])?$values['class']:array($values['class']);
2620 }
2621 }
2622 foreach($checks as $name => $value){
2623 foreach($value['CLASSES_REQUIRED'] as $class){
2625 if(!isset($objectclasses[$name])){
2626 if($value['IS_MUST_HAVE']){
2627 $checks[$name]['STATUS'] = FALSE;
2628 $checks[$name]['MSG'] = sprintf(_("Required object class %s is missing!"), bold($class));
2629 } else {
2630 $checks[$name]['STATUS'] = TRUE;
2631 $checks[$name]['MSG'] = sprintf(_("Optional object class %s is missing!"), bold($class));
2632 }
2633 }elseif(!check_schema_version($objectclasses[$name],$value['REQUIRED_VERSION'])){
2634 $checks[$name]['STATUS'] = FALSE;
2636 $checks[$name]['MSG'] = sprintf(_("Wrong version of required object class %s (!=%s) detected!"), bold($class), bold($value['REQUIRED_VERSION']));
2637 }else{
2638 $checks[$name]['STATUS'] = TRUE;
2639 $checks[$name]['MSG'] = sprintf(_("Class available"));
2640 }
2641 }
2642 }
2644 $tmp = $objectclasses;
2646 /* The gosa base schema */
2647 $checks['posixGroup'] = $def_check;
2648 $checks['posixGroup']['REQUIRED_VERSION'] = "2.6.1";
2649 $checks['posixGroup']['SCHEMA_FILES'] = array("gosa-samba3.schema","gosa-samba2.schema");
2650 $checks['posixGroup']['CLASSES_REQUIRED'] = array("posixGroup");
2651 $checks['posixGroup']['STATUS'] = TRUE;
2652 $checks['posixGroup']['IS_MUST_HAVE'] = TRUE;
2653 $checks['posixGroup']['MSG'] = "";
2654 $checks['posixGroup']['INFO'] = "";
2656 /* Depending on selected rfc2307bis mode, we need different schema configurations */
2657 if(isset($tmp['posixGroup'])){
2659 if($rfc2307bis && isset($tmp['posixGroup']['STRUCTURAL'])){
2660 $checks['posixGroup']['STATUS'] = FALSE;
2661 $checks['posixGroup']['MSG'] = _("RFC2307bis schema is enabled, but the current LDAP configuration does not support it!");
2662 $checks['posixGroup']['INFO'] = _("To use RFC2307bis groups, the objectClass 'posixGroup' must be AUXILIARY.");
2663 }
2664 if(!$rfc2307bis && !isset($tmp['posixGroup']['STRUCTURAL'])){
2665 $checks['posixGroup']['STATUS'] = FALSE;
2666 $checks['posixGroup']['MSG'] = _("RFC2307bis schema is disabled, but the current LDAP configuration supports it!");
2667 $checks['posixGroup']['INFO'] = _("To correct this, the objectClass 'posixGroup' must be STRUCTURAL.");
2668 }
2669 }
2671 return($checks);
2672 }
2675 function get_languages($languages_in_own_language = FALSE,$strip_region_tag = FALSE)
2676 {
2677 $tmp = array(
2678 "de_DE" => "German",
2679 "fr_FR" => "French",
2680 "it_IT" => "Italian",
2681 "es_ES" => "Spanish",
2682 "en_US" => "English",
2683 "nl_NL" => "Dutch",
2684 "pl_PL" => "Polish",
2685 "pt_BR" => "Brazilian Portuguese",
2686 #"sv_SE" => "Swedish",
2687 "zh_CN" => "Chinese",
2688 "vi_VN" => "Vietnamese",
2689 "ru_RU" => "Russian");
2691 $tmp2= array(
2692 "de_DE" => _("German"),
2693 "fr_FR" => _("French"),
2694 "it_IT" => _("Italian"),
2695 "es_ES" => _("Spanish"),
2696 "en_US" => _("English"),
2697 "nl_NL" => _("Dutch"),
2698 "pl_PL" => _("Polish"),
2699 "pt_BR" => _("Brazilian Portuguese"),
2700 #"sv_SE" => _("Swedish"),
2701 "zh_CN" => _("Chinese"),
2702 "vi_VN" => _("Vietnamese"),
2703 "ru_RU" => _("Russian"));
2705 $ret = array();
2706 if($languages_in_own_language){
2708 $old_lang = setlocale(LC_ALL, 0);
2710 /* If the locale wasn't correclty set before, there may be an incorrect
2711 locale returned. Something like this:
2712 C_CTYPE=de_DE.UTF-8;LC_NUMERIC=C;LC_TIME=de_DE.UTF-8;LC ...
2713 Extract the locale name from this string and use it to restore old locale.
2714 */
2715 if(preg_match("/LC_CTYPE/",$old_lang)){
2716 $old_lang = preg_replace("/^.*LC_CTYPE=([^;]*).*$/","\\1",$old_lang);
2717 }
2719 foreach($tmp as $key => $name){
2720 $lang = $key.".UTF-8";
2721 setlocale(LC_ALL, $lang);
2722 if($strip_region_tag){
2723 $ret[preg_replace("/^([^_]*).*$/","\\1",$key)] = _($name)." (".$tmp2[$key].")";
2724 }else{
2725 $ret[$key] = _($name)." (".$tmp2[$key].")";
2726 }
2727 }
2728 setlocale(LC_ALL, $old_lang);
2729 }else{
2730 foreach($tmp as $key => $name){
2731 if($strip_region_tag){
2732 $ret[preg_replace("/^([^_]*).*/","\\1",$key)] = _($name);
2733 }else{
2734 $ret[$key] = _($name);
2735 }
2736 }
2737 }
2738 return($ret);
2739 }
2742 /*! \brief Returns contents of the given POST variable and check magic quotes settings
2743 *
2744 * Depending on the magic quotes settings this returns a stripclashed'ed version of
2745 * a certain POST variable.
2746 *
2747 * \param string 'name' the POST var to return ($_POST[$name])
2748 * \return string
2749 * */
2750 function get_post($name)
2751 {
2752 if(!isset($_POST[$name])){
2753 trigger_error("Requested POST value (".$name.") does not exist, you should add a check to prevent this message.");
2754 return(FALSE);
2755 }
2757 // Handle Posted Arrays
2758 $tmp = array();
2759 if(is_array($_POST[$name]) && !is_string($_POST[$name])){
2760 foreach($_POST[$name] as $key => $val){
2761 if(get_magic_quotes_gpc()){
2762 $val = stripcslashes($val);
2763 }
2764 $tmp[$key] = $val;
2765 }
2766 return($tmp);
2767 }else{
2769 if(get_magic_quotes_gpc()){
2770 $val = stripcslashes($_POST[$name]);
2771 }else{
2772 $val = $_POST[$name];
2773 }
2774 }
2775 return($val);
2776 }
2779 /*! \brief Returns contents of the given POST variable and check magic quotes settings
2780 *
2781 * Depending on the magic quotes settings this returns a stripclashed'ed version of
2782 * a certain POST variable.
2783 *
2784 * \param string 'name' the POST var to return ($_POST[$name])
2785 * \return string
2786 * */
2787 function get_binary_post($name)
2788 {
2789 if(!isset($_POST[$name])){
2790 trigger_error("Requested POST value (".$name.") does not exists, you should add a check to prevent this message.");
2791 return(FALSE);
2792 }
2794 $p = str_replace('\0', '', $_POST[$name]);
2795 if(get_magic_quotes_gpc()){
2796 return(stripcslashes($p));
2797 }else{
2798 return($_POST[$p]);
2799 }
2800 }
2802 function set_post($value)
2803 {
2804 // Take care of array, recursivly convert each array entry.
2805 if(is_array($value)){
2806 foreach($value as $key => $val){
2807 $value[$key] = set_post($val);
2808 }
2809 return($value);
2810 }
2812 // Do not touch boolean values, we may break them.
2813 if($value === TRUE || $value === FALSE ) return($value);
2815 // Return a fixed string which can then be used in HTML fields without
2816 // breaking the layout or the values. This allows to use '"<> in input fields.
2817 return(htmlentities($value, ENT_QUOTES, 'utf-8'));
2818 }
2821 /*! \brief Return class name in correct case */
2822 function get_correct_class_name($cls)
2823 {
2824 global $class_mapping;
2825 if(isset($class_mapping) && is_array($class_mapping)){
2826 foreach($class_mapping as $class => $file){
2827 if(preg_match("/^".$cls."$/i",$class)){
2828 return($class);
2829 }
2830 }
2831 }
2832 return(FALSE);
2833 }
2836 /*! \brief Change the password for a given object ($dn).
2837 * This method uses the specified hashing method to generate a new password
2838 * for the object and it also takes care of sambaHashes, if enabled.
2839 * Finally the postmodify hook of the class 'user' will be called, if it is set.
2840 *
2841 * @param String The DN whose password shall be changed.
2842 * @param String The new password.
2843 * @param Boolean Skip adding samba hashes to the target (sambaNTPassword,sambaLMPassword)
2844 * @param String The hashin method to use, default is the global configured default.
2845 * @param String The users old password, this allows script based rollback mechanisms,
2846 * the prehook will then be called witch switched newPassword/oldPassword.
2847 * @return Boolean TRUE on success else FALSE.
2848 */
2849 function change_password ($dn, $password, $mode=FALSE, $hash= "", $old_password = "", &$message = "")
2850 {
2851 global $config;
2852 $newpass= "";
2854 // Not sure, why this is here, but maybe some encryption methods require it.
2855 mt_srand((double) microtime()*1000000);
2857 // Get a list of all available password encryption methods.
2858 $methods = new passwordMethod(session::get('config'),$dn);
2859 $available = $methods->get_available_methods();
2861 // Fetch the current object data, to be able to detect the current hashing method
2862 // and to be able to rollback changes once has an error occured.
2863 $ldap = $config->get_ldap_link();
2864 $ldap->cat ($dn, array("shadowLastChange", "userPassword","sambaNTPassword","sambaLMPassword", "uid"));
2865 $attrs = $ldap->fetch ();
2866 $initialAttrs = $attrs;
2868 // If no hashing method is enforced, then detect what method we've to use.
2869 $hash = strtolower($hash);
2870 if(empty($hash)){
2872 // Do we need clear-text password for this object?
2873 if(isset($attrs['userPassword'][0]) && !preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0])){
2874 $hash = "clear";
2875 $test = new $available[$hash]($config,$dn);
2876 $test->set_hash($hash);
2877 }
2879 // If we've still no valid hashing method detected, then try to extract if from the userPassword attribute.
2880 elseif(isset($attrs['userPassword'][0]) && preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0], $matches)){
2881 $test = passwordMethod::get_method($attrs['userPassword'][0],$dn);
2882 $hash = $test->get_hash_name();
2883 }
2885 // No current password was found and no hash is enforced, so we've to use the config default here.
2886 $hash = $config->get_cfg_value('core','passwordDefaultHash');
2887 $test = new $available[$hash]($config,$dn);
2888 $test->set_hash($hash);
2889 }else{
2890 $test = new $available[$hash]($config,$dn);
2891 $test->set_hash($hash);
2892 }
2894 // We've now a valid password-method-handle and can create the new password hash or don't we?
2895 if(!$test instanceOf passwordMethod){
2896 $message = _("Cannot detect password hash!");
2897 }else{
2899 // Feed password backends with object information.
2900 $test->dn = $dn;
2901 $test->attrs = $attrs;
2902 $newpass= $test->generate_hash($password);
2904 // Do we have to append samba attributes too?
2905 // - sambaNTPassword / sambaLMPassword
2906 $tmp = $config->get_cfg_value('core','sambaHashHook');
2907 $attrs= array();
2908 if (!$mode && !empty($tmp)){
2909 $attrs= generate_smb_nt_hash($password);
2910 if(!count($attrs) || !is_array($attrs)){
2911 msg_dialog::display(_("Error"),_("Cannot generate SAMBA hash!"),ERROR_DIALOG);
2912 return(FALSE);
2913 }else{
2914 $shadow = (isset($attrs["shadowLastChange"][0]))?(int)(date("U") / 86400):0;
2915 if ($shadow != 0){
2916 $attrs['shadowLastChange']= $shadow;
2917 }
2918 }
2919 }
2921 // Write back the new password hash
2922 $ldap->cd($dn);
2923 $attrs['userPassword']= $newpass;
2925 // Prepare a special attribute list, which will be used for event hook calls
2926 $attrsEvent = array();
2927 foreach($initialAttrs as $name => $value){
2928 if(!is_numeric($name))
2929 $attrsEvent[$name] = escapeshellarg($value[0]);
2930 }
2931 $attrsEvent['dn'] = escapeshellarg($initialAttrs['dn']);
2932 foreach($attrs as $name => $value){
2933 $attrsEvent[$name] = escapeshellarg($value);
2934 }
2935 $attrsEvent['current_password'] = escapeshellarg($old_password);
2936 $attrsEvent['new_password'] = escapeshellarg($password);
2938 // Call the premodify hook now
2939 $passwordPlugin = new password($config,$dn);
2940 plugin::callHook($passwordPlugin, 'PREMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
2941 if($retCode === 0 && count($output)){
2942 $message = sprintf(_("Pre-event hook reported a problem: %s. Password change canceled!"),implode($output));
2943 return(FALSE);
2944 }
2946 // Perform ldap operations
2947 $ldap->modify($attrs);
2949 // Check if the object was locked before, if it was, lock it again!
2950 $deactivated = $test->is_locked($config,$dn);
2951 if($deactivated){
2952 $test->lock_account($config,$dn);
2953 }
2955 // Check if everything went fine and then call the post event hooks.
2956 // If an error occures, then try to rollback the complete actions done.
2957 $preRollback = FALSE;
2958 $ldapRollback = FALSE;
2959 $success = TRUE;
2960 if (!$ldap->success()) {
2961 new log("modify","users/passwordMethod",$dn,array(),"Password change - ldap modifications! - FAILED");
2962 $success =FALSE;
2963 $message = msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD);
2964 $preRollback =TRUE;
2965 } else {
2967 // Now call the passwordMethod change mechanism.
2968 if(!$test->set_password($password)){
2969 $ldapRollback = TRUE;
2970 $preRollback =TRUE;
2971 $success = FALSE;
2972 new log("modify","users/passwordMethod",$dn,array(),"Password change - set_password! - FAILED");
2973 $message = _("Password change failed!");
2974 }else{
2976 // Execute the password hook
2977 plugin::callHook($passwordPlugin, 'POSTMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
2978 if($retCode === 0){
2979 if(count($output)){
2980 new log("modify","users/passwordMethod",$dn,array(),"Password change - Post modify hook reported! - FAILED!");
2981 $message = sprintf(_("Post-event hook reported a problem: %s. Password change canceled!"),implode($output));
2982 $ldapRollback = TRUE;
2983 $preRollback = TRUE;
2984 $success = FALSE;
2985 }else{
2986 #new log("modify","users/passwordMethod",$dn,array(),"Password change - successfull!");
2987 }
2988 }else{
2989 $ldapRollback = TRUE;
2990 $preRollback = TRUE;
2991 $success = FALSE;
2992 new log("modify","users/passwordMethod",$dn,array(),"Password change - postmodify hook execution! - FAILED");
2993 new log("modify","users/passwordMethod",$dn,array(),$error);
2995 // Call password method again and send in old password to
2996 // keep the database consistency
2997 $test->set_password($old_password);
2998 }
2999 }
3000 }
3002 // Setting the password in the ldap database or further operation failed, we should now execute
3003 // the plugins pre-event hook, using switched passwords, new/old password.
3004 // This ensures that passwords which were set outside of GOsa, will be reset to its
3005 // starting value.
3006 if($preRollback){
3007 new log("modify","users/passwordMethod",$dn,array(),"Rolling back premodify hook!");
3008 $oldpass= $test->generate_hash($old_password);
3009 $attrsEvent['current_password'] = escapeshellarg($password);
3010 $attrsEvent['new_password'] = escapeshellarg($old_password);
3011 foreach(array("userPassword","sambaNTPassword","sambaLMPassword") as $attr){
3012 if(isset($initialAttrs[$attr][0])) $attrsEvent[$attr] = $initialAttrs[$attr][0];
3013 }
3015 plugin::callHook($passwordPlugin, 'PREMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
3016 if($retCode === 0 && count($output)){
3017 $message = sprintf(_("Pre-event hook reported a problem: %s. Password change canceled!"),implode($output));
3018 new log("modify","users/passwordMethod",$dn,array(),"Rolling back premodify hook! - FAILED!");
3019 }
3020 }
3022 // We've written the password to the ldap database, but executing the postmodify hook failed.
3023 // Now, we've to rollback all password related ldap operations.
3024 if($ldapRollback){
3025 new log("modify","users/passwordMethod",$dn,array(),"Rolling back ldap modifications!");
3026 $attrs = array();
3027 foreach(array("userPassword","sambaNTPassword","sambaLMPassword") as $attr){
3028 if(isset($initialAttrs[$attr][0])) $attrs[$attr] = $initialAttrs[$attr][0];
3029 }
3030 $ldap->cd($dn);
3031 $ldap->modify($attrs);
3032 if(!$ldap->success()){
3033 $message = msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD);
3034 new log("modify","users/passwordMethod",$dn,array(),"Rolling back ldap modifications! - FAILED");
3035 }
3036 }
3038 // Log action.
3039 if($success){
3040 stats::log('global', 'global', array('users'), $action = 'change_password', $amount = 1, 0, $test->get_hash());
3041 new log("modify","users/passwordMethod",$dn,array(),"Password change - successfull!");
3042 }else{
3043 new log("modify","users/passwordMethod",$dn,array(),"Password change - FAILED!");
3044 }
3046 return($success);
3047 }
3048 }
3051 /*! \brief Generate samba hashes
3052 *
3053 * Given a certain password this constructs an array like
3054 * array['sambaLMPassword'] etc.
3055 *
3056 * \param string 'password'
3057 * \return array contains several keys for lmPassword, ntPassword, pwdLastSet, etc. depending
3058 * on the samba version
3059 */
3060 function generate_smb_nt_hash($password)
3061 {
3062 global $config;
3064 // First try to retrieve values via RPC
3065 if ($config->get_cfg_value("core","gosaRpcServer") != ""){
3067 $rpc = $config->getRpcHandle();
3068 $hash = $rpc->mksmbhash($password);
3069 if(!$rpc->success()){
3070 msg_dialog::display(_("Error"),msgPool::rpcError($rpc->get_error()),ERROR_DIALOG);
3071 return(array());
3072 }
3074 }elseif ($config->get_cfg_value("core","gosaSupportURI") != ""){
3076 // Try using gosa-si
3077 $res= gosaSupportDaemon::send("gosa_gen_smb_hash", "GOSA", array("password" => $password), TRUE);
3078 if (isset($res['XML']['HASH'])){
3079 $hash= $res['XML']['HASH'];
3080 } else {
3081 $hash= "";
3082 }
3084 if ($hash == "") {
3085 msg_dialog::display(_("Configuration error"), _("Cannot generate SAMBA hash!"), ERROR_DIALOG);
3086 return ("");
3087 }
3088 } else {
3089 $tmp = $config->get_cfg_value("core",'sambaHashHook');
3090 $tmp = preg_replace("/%userPassword/", escapeshellarg($password), $tmp);
3091 $tmp = preg_replace("/%password/", escapeshellarg($password), $tmp);
3092 @DEBUG (DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $tmp, "Execute");
3094 exec($tmp, $ar);
3095 flush();
3096 reset($ar);
3097 $hash= current($ar);
3099 if ($hash == "") {
3100 msg_dialog::display(_("Configuration error"), sprintf(_("Generating SAMBA hash by running %s failed: check %s!"), bold($config->get_cfg_value("core",'sambaHashHook'), bold("sambaHashHook"))), ERROR_DIALOG);
3101 return(array());
3102 }
3103 }
3105 list($lm,$nt)= explode(":", trim($hash));
3107 $attrs['sambaLMPassword']= $lm;
3108 $attrs['sambaNTPassword']= $nt;
3109 $attrs['sambaPwdLastSet']= date('U');
3110 $attrs['sambaBadPasswordCount']= "0";
3111 $attrs['sambaBadPasswordTime']= "0";
3112 return($attrs);
3113 }
3116 /*! \brief Get the Change Sequence Number of a certain DN
3117 *
3118 * To verify if a given object has been changed outside of Gosa
3119 * in the meanwhile, this function can be used to get the entryCSN
3120 * from the LDAP directory. It uses the attribute as configured
3121 * in modificationDetectionAttribute
3122 *
3123 * \param string 'dn'
3124 * \return either the result or "" in any other case
3125 */
3126 function getEntryCSN($dn)
3127 {
3128 global $config;
3129 if(empty($dn) || !is_object($config)){
3130 return("");
3131 }
3133 /* Get attribute that we should use as serial number */
3134 $attr= $config->get_cfg_value("core","modificationDetectionAttribute");
3135 if($attr != ""){
3136 $ldap = $config->get_ldap_link();
3137 $ldap->cat($dn,array($attr));
3138 $csn = $ldap->fetch();
3139 if(isset($csn[$attr][0])){
3140 return($csn[$attr][0]);
3141 }
3142 }
3143 return("");
3144 }
3147 /*! \brief Add (a) given objectClass(es) to an attrs entry
3148 *
3149 * The function adds the specified objectClass(es) to the given
3150 * attrs entry.
3151 *
3152 * \param mixed 'classes' Either a single objectClass or several objectClasses
3153 * as an array
3154 * \param array 'attrs' The attrs array to be modified.
3155 *
3156 * */
3157 function add_objectClass($classes, &$attrs)
3158 {
3159 if (is_array($classes)){
3160 $list= $classes;
3161 } else {
3162 $list= array($classes);
3163 }
3165 foreach ($list as $class){
3166 $attrs['objectClass'][]= $class;
3167 }
3168 }
3171 /*! \brief Removes a given objectClass from the attrs entry
3172 *
3173 * Similar to add_objectClass, except that it removes the given
3174 * objectClasses. See it for the params.
3175 * */
3176 function remove_objectClass($classes, &$attrs)
3177 {
3178 if (isset($attrs['objectClass'])){
3179 /* Array? */
3180 if (is_array($classes)){
3181 $list= $classes;
3182 } else {
3183 $list= array($classes);
3184 }
3186 $tmp= array();
3187 foreach ($attrs['objectClass'] as $oc) {
3188 foreach ($list as $class){
3189 if (strtolower($oc) != strtolower($class)){
3190 $tmp[]= $oc;
3191 }
3192 }
3193 }
3194 $attrs['objectClass']= $tmp;
3195 }
3196 }
3199 /*! \brief Initialize a file download with given content, name and data type.
3200 * \param string data The content to send.
3201 * \param string name The name of the file.
3202 * \param string type The content identifier, default value is "application/octet-stream";
3203 */
3204 function send_binary_content($data,$name,$type = "application/octet-stream")
3205 {
3206 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
3207 header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
3208 header("Cache-Control: no-cache");
3209 header("Pragma: no-cache");
3210 header("Cache-Control: post-check=0, pre-check=0");
3211 header("Content-type: ".$type."");
3213 $HTTP_USER_AGENT = $_SERVER['HTTP_USER_AGENT'];
3215 /* Strip name if it is a complete path */
3216 if (preg_match ("/\//", $name)) {
3217 $name= basename($name);
3218 }
3220 /* force download dialog */
3221 if (preg_match('/MSIE 5.5/', $HTTP_USER_AGENT) || preg_match('/MSIE 6.0/', $HTTP_USER_AGENT)) {
3222 header('Content-Disposition: filename="'.$name.'"');
3223 } else {
3224 header('Content-Disposition: attachment; filename="'.$name.'"');
3225 }
3227 echo $data;
3228 exit();
3229 }
3232 function reverse_html_entities($str,$type = ENT_QUOTES , $charset = "UTF-8")
3233 {
3234 if(is_string($str)){
3235 return(htmlentities($str,$type,$charset));
3236 }elseif(is_array($str)){
3237 foreach($str as $name => $value){
3238 $str[$name] = reverse_html_entities($value,$type,$charset);
3239 }
3240 }
3241 return($str);
3242 }
3245 /*! \brief Encode special string characters so we can use the string in \
3246 HTML output, without breaking quotes.
3247 \param string The String we want to encode.
3248 \return string The encoded String
3249 */
3250 function xmlentities($str)
3251 {
3252 if(is_string($str)){
3254 static $asc2uni= array();
3255 if (!count($asc2uni)){
3256 for($i=128;$i<256;$i++){
3257 # $asc2uni[chr($i)] = "&#x".dechex($i).";";
3258 }
3259 }
3261 $str = str_replace("&", "&", $str);
3262 $str = str_replace("<", "<", $str);
3263 $str = str_replace(">", ">", $str);
3264 $str = str_replace("'", "'", $str);
3265 $str = str_replace("\"", """, $str);
3266 $str = str_replace("\r", "", $str);
3267 $str = strtr($str,$asc2uni);
3268 return $str;
3269 }elseif(is_array($str)){
3270 foreach($str as $name => $value){
3271 $str[$name] = xmlentities($value);
3272 }
3273 }
3274 return($str);
3275 }
3278 /*! \brief Updates all accessTo attributes from a given value to a new one.
3279 For example if a host is renamed.
3280 \param String $from The source accessTo name.
3281 \param String $to The destination accessTo name.
3282 */
3283 function update_accessTo($from,$to)
3284 {
3285 global $config;
3286 $ldap = $config->get_ldap_link();
3287 $ldap->cd($config->current['BASE']);
3288 $ldap->search("(&(objectClass=trustAccount)(accessTo=".$from."))",array("objectClass","accessTo"));
3289 while($attrs = $ldap->fetch()){
3290 $new_attrs = array("accessTo" => array());
3291 $dn = $attrs['dn'];
3292 for($i = 0 ; $i < $attrs['accessTo']['count']; $i++){
3293 if($attrs['accessTo'][$i] == $from){
3294 if(!empty($to)){
3295 $new_attrs['accessTo'][] = $to;
3296 }
3297 }else{
3298 $new_attrs['accessTo'][] = $attrs['accessTo'][$i];
3299 }
3300 }
3301 $ldap->cd($dn);
3302 $ldap->modify($new_attrs);
3303 if (!$ldap->success()){
3304 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD, "update_accessTo($from,$to)"));
3305 }
3306 new log("modify","update_accessTo($from,$to)",$dn,array_keys($new_attrs),$ldap->get_error());
3307 }
3308 }
3311 /*! \brief Returns a random char */
3312 function get_random_char () {
3313 $randno = rand (0, 63);
3314 if ($randno < 12) {
3315 return (chr ($randno + 46)); // Digits, '/' and '.'
3316 } else if ($randno < 38) {
3317 return (chr ($randno + 53)); // Uppercase
3318 } else {
3319 return (chr ($randno + 59)); // Lowercase
3320 }
3321 }
3324 function cred_encrypt($input, $password) {
3326 $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
3327 $iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
3329 return bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $password, $input, MCRYPT_MODE_ECB, $iv));
3331 }
3334 function cred_decrypt($input,$password) {
3335 $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
3336 $iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
3338 return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, pack("H*", $input), MCRYPT_MODE_ECB, $iv);
3339 }
3342 function get_object_info()
3343 {
3344 return(session::get('objectinfo'));
3345 }
3348 function set_object_info($str = "")
3349 {
3350 session::set('objectinfo',$str);
3351 }
3354 function isIpInNet($ip, $net, $mask) {
3355 // Move to long ints
3356 $ip= ip2long($ip);
3357 $net= ip2long($net);
3358 $mask= ip2long($mask);
3360 // Mask given IP with mask. If it returns "net", we're in...
3361 $res= $ip & $mask;
3363 return ($res == $net);
3364 }
3367 function get_next_id($attrib, $dn)
3368 {
3369 global $config;
3371 switch ($config->get_cfg_value("core","idAllocationMethod")){
3372 case "pool":
3373 return get_next_id_pool($attrib);
3374 case "traditional":
3375 return get_next_id_traditional($attrib, $dn);
3376 }
3378 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("unknown idAllocation method!"), ERROR_DIALOG);
3379 return null;
3380 }
3383 function get_next_id_pool($attrib) {
3384 global $config;
3386 /* Fill informational values */
3387 $min= $config->get_cfg_value("core","${attrib}PoolMin");
3388 $max= $config->get_cfg_value("core","${attrib}PoolMax");
3390 /* Sanity check */
3391 if ($min >= $max) {
3392 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." ".sprintf(_("%sPoolMin >= %sPoolMax!"), bold($attrib), bold($attrib)), ERROR_DIALOG);
3393 return null;
3394 }
3396 /* ID to skip */
3397 $ldap= $config->get_ldap_link();
3398 $id= null;
3400 /* Try to allocate the ID several times before failing */
3401 $tries= 3;
3402 while ($tries--) {
3404 /* Look for ID map entry */
3405 $ldap->cd ($config->current['BASE']);
3406 $ldap->search ("(&(objectClass=sambaUnixIdPool)($attrib=*))", array("$attrib"));
3408 /* If it does not exist, create one with these defaults */
3409 if ($ldap->count() == 0) {
3410 /* Fill informational values */
3411 $minUserId= $config->get_cfg_value("core","uidNumberPoolMin");
3412 $minGroupId= $config->get_cfg_value("core","gidNumberPoolMin");
3414 /* Add as default */
3415 $attrs= array("objectClass" => array("organizationalUnit", "sambaUnixIdPool"));
3416 $attrs["ou"]= "idmap";
3417 $attrs["uidNumber"]= $minUserId;
3418 $attrs["gidNumber"]= $minGroupId;
3419 $ldap->cd("ou=idmap,".$config->current['BASE']);
3420 $ldap->add($attrs);
3421 if ($ldap->error != "Success") {
3422 msg_dialog::display(_("Error"), _("Cannot create sambaUnixIdPool entry!"), ERROR_DIALOG);
3423 return null;
3424 }
3425 $tries++;
3426 continue;
3427 }
3428 /* Bail out if it's not unique */
3429 if ($ldap->count() != 1) {
3430 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("sambaUnixIdPool is not unique!"), ERROR_DIALOG);
3431 return null;
3432 }
3434 /* Store old attrib and generate new */
3435 $attrs= $ldap->fetch();
3436 $dn= $ldap->getDN();
3437 $oldAttr= $attrs[$attrib][0];
3438 $newAttr= $oldAttr + 1;
3440 /* Sanity check */
3441 if ($newAttr >= $max) {
3442 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("no ID available!"), ERROR_DIALOG);
3443 return null;
3444 }
3445 if ($newAttr < $min) {
3446 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("no ID available!"), ERROR_DIALOG);
3447 return null;
3448 }
3450 #FIXME: PHP is not able to do a modification of "del: .../add: ...", so this
3451 # is completely unsafe in the moment.
3452 #/* Remove old attr, add new attr */
3453 #$attrs= array($attrib => $oldAttr);
3454 #$ldap->rm($attrs, $dn);
3455 #if ($ldap->error != "Success") {
3456 # continue;
3457 #}
3458 $ldap->cd($dn);
3459 $ldap->modify(array($attrib => $newAttr));
3460 if ($ldap->error != "Success") {
3461 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." ".$ldap->get_error(), ERROR_DIALOG);
3462 return null;
3463 } else {
3464 return $oldAttr;
3465 }
3466 }
3468 /* Bail out if we had problems getting the next id */
3469 if (!$tries) {
3470 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("maximum number of tries exceeded!"), ERROR_DIALOG);
3471 }
3473 return $id;
3474 }
3477 function get_next_id_traditional($attrib, $dn)
3478 {
3479 global $config;
3481 $ids= array();
3482 $ldap= $config->get_ldap_link();
3484 $ldap->cd ($config->current['BASE']);
3485 if (preg_match('/gidNumber/i', $attrib)){
3486 $oc= "posixGroup";
3487 } else {
3488 $oc= "posixAccount";
3489 }
3490 $ldap->search ("(&(objectClass=$oc)($attrib=*))", array("$attrib"));
3492 /* Get list of ids */
3493 while ($attrs= $ldap->fetch()){
3494 $ids[]= (int)$attrs["$attrib"][0];
3495 }
3497 /* Add the nobody id */
3498 $ids[]= 65534;
3500 /* get the ranges */
3501 $tmp = array('0'=> 1000);
3502 if (preg_match('/posixAccount/', $oc) && $config->get_cfg_value("core","uidNumberBase") != ""){
3503 $tmp= explode('-',$config->get_cfg_value("core","uidNumberBase"));
3504 } elseif($config->get_cfg_value("core","gidNumberBase") != ""){
3505 $tmp= explode('-',$config->get_cfg_value("core","gidNumberBase"));
3506 }
3508 /* Set hwm to max if not set - for backward compatibility */
3509 $lwm= $tmp[0];
3510 if (isset($tmp[1])){
3511 $hwm= $tmp[1];
3512 } else {
3513 $hwm= pow(2,32);
3514 }
3515 /* Find out next free id near to UID_BASE */
3516 if ($config->get_cfg_value("core","baseIdHook") == ""){
3517 $base= $lwm;
3518 } else {
3519 /* Call base hook */
3520 $base= get_base_from_hook($dn, $attrib);
3521 }
3522 for ($id= $base; $id++; $id < pow(2,32)){
3523 if (!in_array_strict($id, $ids)){
3524 return ($id);
3525 }
3526 }
3528 /* Should not happen */
3529 if ($id == $hwm){
3530 msg_dialog::display(_("Error"), _("Cannot allocate free ID!"), ERROR_DIALOG);
3531 exit;
3532 }
3533 }
3536 /* Mark the occurance of a string with a span */
3537 function mark($needle, $haystack, $ignorecase= true)
3538 {
3539 $result= "";
3541 while (preg_match('/^(.*)('.preg_quote($needle).')(.*)$/i', $haystack, $matches)) {
3542 $result.= $matches[1]."<span class='mark'>".$matches[2]."</span>";
3543 $haystack= $matches[3];
3544 }
3546 return $result.$haystack;
3547 }
3550 /* Return an image description using the path */
3551 function image($path, $action= "", $title= "", $align= "middle")
3552 {
3553 global $config;
3554 global $BASE_DIR;
3555 $label= null;
3557 // Bail out, if there's no style file
3558 if(!class_exists('session')){
3559 return "";
3560 }
3561 if(!session::global_is_set("img-styles")){
3563 // Get theme
3564 if (isset ($config)){
3565 $theme= $config->get_cfg_value("core","theme");
3566 } else {
3568 // Fall back to default theme
3569 $theme= "default";
3570 }
3572 if (!file_exists("$BASE_DIR/ihtml/themes/$theme/img.styles")){
3573 die ("No img.style for this theme found!");
3574 }
3576 session::global_set('img-styles', unserialize(file_get_contents("$BASE_DIR/ihtml/themes/$theme/img.styles")));
3577 }
3578 $styles= session::global_get('img-styles');
3580 /* Extract labels from path */
3581 if (preg_match("/\.png\[(.*)\]$/", $path, $matches)) {
3582 $label= $matches[1];
3583 }
3585 $lbl= "";
3586 if ($label) {
3587 if (isset($styles["images/label-".$label.".png"])) {
3588 $lbl= "<div style='".$styles["images/label-".$label.".png"]."'></div>";
3589 } else {
3590 die("Invalid label specified: $label\n");
3591 }
3593 $path= preg_replace("/\[.*\]$/", "", $path);
3594 }
3596 // Non middle layout?
3597 if ($align == "middle") {
3598 $align= "";
3599 } else {
3600 $align= ";vertical-align:$align";
3601 }
3603 // Clickable image or not?
3604 if ($title != "") {
3605 $title= "title='$title'";
3606 }
3607 if ($action == "") {
3608 return "<div class='img' $title style='".$styles[$path]."$align'>$lbl</div>";
3609 } else {
3610 return "<input type='submit' class='img' id='$action' value='' name='$action' $title style='".$styles[$path]."$align'>";
3611 }
3612 }
3614 /*! \brief Encodes a complex string to be useable in HTML posts.
3615 */
3616 function postEncode($str)
3617 {
3618 return(preg_replace("/=/","_", base64_encode($str)));
3619 }
3621 /*! \brief Decodes a string encoded by postEncode
3622 */
3623 function postDecode($str)
3624 {
3625 return(base64_decode(preg_replace("/_/","=", $str)));
3626 }
3629 /*! \brief Generate styled output
3630 */
3631 function bold($str)
3632 {
3633 return "<span class='highlight'>$str</span>";
3634 }
3638 /*! \brief Detect the special character handling for the currently used ldap database.
3639 * For example some convert , to \2C or " to \22.
3640 *
3641 * @param Config The GOsa configuration object.
3642 * @return Array An array containing a character mapping the use.
3643 */
3644 function detectLdapSpecialCharHandling()
3645 {
3646 // The list of chars to test for
3647 global $config;
3648 if(!$config) return(NULL);
3650 // In the DN we've to use escaped characters, but the object name (o)
3651 // has the be un-escaped.
3652 $name = 'GOsaLdapEncoding_,_"_(_)_+_/';
3653 $dnName = 'GOsaLdapEncoding_\,_\"_(_)_\+_/';
3655 // Prapare name to be useable in filters
3656 $fixed= normalizeLdap(str_replace('\\\\', '\\\\\\', $name));
3657 $filterName = str_replace('\\,', '\\\\,', $fixed);
3659 // Create the target dn
3660 $oDN = "o={$dnName},".$config->current['BASE'];
3662 // Get ldap connection and check if we've already created the character
3663 // detection object.
3664 $ldapCID = ldap_connect($config->current['SERVER']);
3665 ldap_set_option($ldapCID, LDAP_OPT_PROTOCOL_VERSION, 3);
3666 ldap_bind($ldapCID, $config->current['ADMINDN'],$config->current['ADMINPASSWORD']);
3667 $res = ldap_list($ldapCID, $config->current['BASE'],
3668 "(&(o=".$filterName.")(objectClass=organization))",
3669 array('dn'));
3671 // If we haven't created the character-detection object, then create it now.
3672 $cnt = ldap_count_entries($ldapCID, $res);
3673 if(!$cnt){
3674 $obj = array();
3675 $obj['objectClass'] = array('top','organization');
3676 $obj['o'] = $name;
3677 $obj['description'] = 'GOsa character encoding test-object.';
3678 if(!@ldap_add($ldapCID, $oDN, $obj)){
3679 trigger_error("GOsa couldn't detect the special character handling used by your ldap!");
3680 return(NULL);
3681 }
3682 }
3684 // Read the character-handling detection entry from the ldap.
3685 $res = ldap_list($ldapCID, $config->current['BASE'],
3686 "(&(o=".$filterName.")(objectClass=organization))",
3687 array('dn','o'));
3688 $cnt = ldap_count_entries($ldapCID, $res);
3689 if($cnt != 1 || !$res){
3690 trigger_error("GOsa couldn't detect the special character handling used by your ldap!");
3691 return(NULL);
3692 }else{
3694 // Get the character handling entry from the ldap and check how the
3695 // values were written. Compare them with what
3696 // we've initially intended to write and create a mapping out
3697 // of the results.
3698 $re = ldap_first_entry($ldapCID, $res);
3699 $attrs = ldap_get_attributes($ldapCID, $re);
3701 // Extract the interessting characters out of the dn and the
3702 // initially used $name for the entry.
3703 $mapDNstr = preg_replace("/^o=GOsaLdapEncoding_([^,]*),.*$/","\\1", trim(ldap_get_dn($ldapCID, $re)));
3704 $mapDN = preg_split("/_/", $mapDNstr,0, PREG_SPLIT_NO_EMPTY);
3706 $mapNameStr = preg_replace("/^GOsaLdapEncoding_/","",$dnName);
3707 $mapName = preg_split("/_/", $mapNameStr,0, PREG_SPLIT_NO_EMPTY);
3709 // Create a mapping out of the results.
3710 $map = array();
3711 foreach($mapName as $key => $entry){
3712 $map[$entry] = $mapDN[$key];
3713 }
3714 return($map);
3715 }
3716 return(NULL);
3717 }
3720 /*! \brief Replaces placeholder in a given string.
3721 * For example:
3722 * '%uid@gonicus.de' Replaces '%uid' with 'uid'.
3723 * '{%uid[0]@gonicus.de}' Replaces '%uid[0]' with the first char of 'uid'.
3724 * '%uid[2-4]@gonicus.de' Replaces '%uid[2-4]' with three chars from 'uid' starting from the second.
3725 *
3726 * The surrounding {} in example 2 are optional.
3727 *
3728 * @param String The string to perform the action on.
3729 * @param Array An array of replacements.
3730 * @return The resulting string.
3731 */
3732 function fillReplacements($str, $attrs, $shellArg = FALSE, $default = "")
3733 {
3734 // Search for '{%...[n-m]}
3735 // Get all matching parts of the given string and sort them by
3736 // length, to avoid replacing strings like '%uidNumber' with 'uid'
3737 // instead of 'uidNumber'; The longest tring at first.
3738 preg_match_all('/(\{?%([a-z0-9_]+)(\[(([0-9_]+)(\-([0-9_]+))?)\])?\}?)/i', $str ,$matches, PREG_SET_ORDER);
3739 $hits = array();
3740 foreach($matches as $match){
3741 $hits[strlen($match[2]).$match[0]] = $match;
3742 }
3743 krsort($hits);
3745 // Add lower case placeholders to avoid errors
3746 foreach($attrs as $key => $attr) $attrs[strtolower($key)] = $attr;
3748 // Replace the placeholder in the given string now.
3749 foreach($hits as $match){
3751 // Avoid errors about undefined index.
3752 $name = strtolower($match[2]);
3753 if(!isset($attrs[$name])) $attrs[$name] = $default;
3755 // Calculate the replacement
3756 $start = (isset($match[5])) ? $match[5] : 0;
3757 $end = strlen($attrs[$name]);
3758 if(isset($match[5]) && !isset($match[7])){
3759 $end = 1;
3760 }elseif(isset($match[5]) && isset($match[7])){
3761 $end = ($match[7]-$start+1);
3762 }
3763 $value = substr($attrs[$name], $start, $end);
3765 // Use values which are valid for shell execution?
3766 if($shellArg) $value = escapeshellarg($value);
3768 // Replace the placeholder within the string.
3769 $str = preg_replace("/".preg_quote($match[0],'/')."/", $value, $str);
3770 }
3771 return($str);
3772 }
3775 /*! \brief Generate a list of uid proposals based on a rule
3776 *
3777 * Unroll given rule string by filling in attributes and replacing
3778 * all keywords.
3779 *
3780 * \param string 'rule' The rule string from gosa.conf.
3781 * \param array 'attributes' A dictionary of attribute/value mappings
3782 * \return array List of valid not used uids
3783 */
3784 function gen_uids($rule, $attributes)
3785 {
3786 global $config;
3787 $ldap = $config->get_ldap_link();
3788 $ldap->cd($config->current['BASE']);
3790 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $rule, "Processing");
3792 // Strip out non ascii chars
3793 foreach($attributes as $name => $value){
3794 if ( $config->get_cfg_value("core", "forceTranslit") == "true" ) {
3795 $value = cyrillic2ascii($value);
3796 } else {
3797 $value = iconv('UTF-8', 'US-ASCII//TRANSLIT', $value);
3798 }
3799 $value = preg_replace('/[^(\x20-\x7F)]*/','',$value);
3800 $attributes[$name] = strtolower($value);
3801 }
3803 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $attributes, "Prepare");
3805 // Search for '{%...[n-m]}
3806 // Get all matching parts of the given string and sort them by
3807 // length, to avoid replacing strings like '%uidNumber' with 'uid'
3808 // instead of 'uidNumber'; The longest tring at first.
3809 preg_match_all('/(\{?%([a-z0-9]+)(\[(([0-9]+)(\-([0-9]+))?)\])?\}?)/i', $rule ,$matches, PREG_SET_ORDER);
3810 $replacements = array();
3811 foreach($matches as $match){
3813 // No start position given, then add the complete value
3814 if(!isset($match[5])){
3815 $replacements[$match[0]][] = $attributes[$match[2]];
3817 // Start given but no end, so just add a single character
3818 }elseif(!isset($match[7])){
3819 if(isset($attributes[$match[2]][$match[5]])){
3820 $tmp = " ".$attributes[$match[2]];
3821 $replacements[$match[0]][] = trim($tmp[$match[5]]);
3822 }
3824 // Add all values in range
3825 }else{
3826 $str = "";
3827 for($i=$match[5]; $i<= $match[7]; $i++){
3828 if(isset($attributes[$match[2]][$i])){
3829 $tmp = " ".$attributes[$match[2]];
3830 $str .= $tmp[$i];
3831 $replacements[$match[0]][] = trim($str);
3832 }
3833 }
3834 }
3835 }
3837 // Create proposal array
3838 $rules = array($rule);
3839 foreach($replacements as $tag => $values){
3840 $rules = gen_uid_proposals($rules, $tag, $values);
3841 }
3844 // Search for id tags {id:3} / {id#3}
3845 preg_match_all('/\{id(#|:)([0-9])+\}/i', $rule, $matches, PREG_SET_ORDER);
3846 $idReplacements = array();
3847 foreach($matches as $match){
3848 if(count($match) != 3) continue;
3850 // Generate random number
3851 if($match[1] == '#'){
3852 foreach($rules as $id => $ruleStr){
3853 $genID = rand(pow(10,$match[2] -1),pow(10, ($match[2])) - 1);
3854 $rules[$id] = preg_replace("/".preg_quote($match[0],'/')."/", $genID,$ruleStr);
3855 }
3856 }
3858 // Search for next free id
3859 if($match[1] == ':'){
3861 // Walk through rules and replace all occurences of {id:..}
3862 foreach($rules as $id => $ruleStr){
3863 $genID = 0;
3864 $start = TRUE;
3865 while($start || $ldap->count()){
3866 $start = FALSE;
3867 $number= sprintf("%0".$match[2]."d", $genID);
3868 $testRule = preg_replace("/".preg_quote($match[0],'/')."/",$number,$ruleStr);
3869 $ldap->search('uid='.normalizeLdap($testRule));
3870 $genID ++;
3871 }
3872 $rules[$id] = preg_replace("/".preg_quote($match[0],'/')."/",$number,$ruleStr);
3873 }
3874 }
3875 }
3877 // Create result set by checking which uid is already used and which is free.
3878 $ret = array();
3879 foreach($rules as $rule){
3880 $ldap->search('uid='.normalizeLdap($rule));
3881 if(!$ldap->count()){
3882 $ret[] = $rule;
3883 }
3884 }
3886 return($ret);
3887 }
3890 function gen_uid_proposals(&$rules, $tag, $values)
3891 {
3892 $newRules = array();
3893 foreach($rules as $rule){
3894 foreach($values as $value){
3895 $newRules[] = preg_replace("/".preg_quote($tag,'/')."/", $value, $rule);
3896 }
3897 }
3898 return($newRules);
3899 }
3902 function gen_uuid()
3903 {
3904 return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
3905 // 32 bits for "time_low"
3906 mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
3908 // 16 bits for "time_mid"
3909 mt_rand( 0, 0xffff ),
3911 // 16 bits for "time_hi_and_version",
3912 // four most significant bits holds version number 4
3913 mt_rand( 0, 0x0fff ) | 0x4000,
3915 // 16 bits, 8 bits for "clk_seq_hi_res",
3916 // 8 bits for "clk_seq_low",
3917 // two most significant bits holds zero and one for variant DCE1.1
3918 mt_rand( 0, 0x3fff ) | 0x8000,
3920 // 48 bits for "node"
3921 mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
3922 );
3923 }
3925 function gosa_file_name($filename)
3926 {
3927 $tempfile = tempnam(sys_get_temp_dir(), 'GOsa');
3928 if(move_uploaded_file($filename, $tempfile)){
3929 return( $tempfile);
3930 }
3931 }
3933 function gosa_file($filename)
3934 {
3935 $tempfile = tempnam(sys_get_temp_dir(), 'GOsa');
3936 if(move_uploaded_file($filename, $tempfile)){
3937 return file( $tempfile );
3938 }
3939 }
3941 function gosa_fopen($filename, $mode)
3942 {
3943 $tempfile = tempnam(sys_get_temp_dir(), 'GOsa');
3944 if(move_uploaded_file($filename, $tempfile)){
3945 return fopen( $tempfile, $mode );
3946 }
3947 }
3950 /*\brief Our own in_array method which defaults to a strict mode.
3951 */
3952 function in_array_strict($needle, $haystack, $strict = TRUE)
3953 {
3954 return(in_array($needle, $haystack, $strict));
3955 }
3957 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
3958 ?>