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 require_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($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 exists, 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 $shadow = (isset($attrs["shadowLastChange"][0]))?(int)(date("U") / 86400):0;
2911 if ($shadow != 0){
2912 $attrs['shadowLastChange']= $shadow;
2913 }
2914 }
2916 // Write back the new password hash
2917 $ldap->cd($dn);
2918 $attrs['userPassword']= $newpass;
2920 // Prepare a special attribute list, which will be used for event hook calls
2921 $attrsEvent = array();
2922 foreach($initialAttrs as $name => $value){
2923 if(!is_numeric($name))
2924 $attrsEvent[$name] = escapeshellarg($value[0]);
2925 }
2926 $attrsEvent['dn'] = escapeshellarg($initialAttrs['dn']);
2927 foreach($attrs as $name => $value){
2928 $attrsEvent[$name] = escapeshellarg($value);
2929 }
2930 $attrsEvent['current_password'] = escapeshellarg($old_password);
2931 $attrsEvent['new_password'] = escapeshellarg($password);
2933 // Call the premodify hook now
2934 $passwordPlugin = new password($config,$dn);
2935 plugin::callHook($passwordPlugin, 'PREMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
2936 if($retCode === 0 && count($output)){
2937 $message = sprintf(_("Pre-event hook reported a problem: %s. Password change canceled!"),implode($output));
2938 return(FALSE);
2939 }
2941 // Perform ldap operations
2942 $ldap->modify($attrs);
2944 // Check if the object was locked before, if it was, lock it again!
2945 $deactivated = $test->is_locked($config,$dn);
2946 if($deactivated){
2947 $test->lock_account($config,$dn);
2948 }
2950 // Check if everything went fine and then call the post event hooks.
2951 // If an error occures, then try to rollback the complete actions done.
2952 $preRollback = FALSE;
2953 $ldapRollback = FALSE;
2954 $success = TRUE;
2955 if (!$ldap->success()) {
2956 new log("modify","users/passwordMethod",$dn,array(),"Password change - ldap modifications! - FAILED");
2957 $success =FALSE;
2958 $message = msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD);
2959 $preRollback =TRUE;
2960 } else {
2962 // Now call the passwordMethod change mechanism.
2963 if(!$test->set_password($password)){
2964 $ldapRollback = TRUE;
2965 $preRollback =TRUE;
2966 $success = FALSE;
2967 new log("modify","users/passwordMethod",$dn,array(),"Password change - set_password! - FAILED");
2968 $message = _("Password change failed!");
2969 }else{
2971 // Execute the password hook
2972 plugin::callHook($passwordPlugin, 'POSTMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
2973 if($retCode === 0){
2974 if(count($output)){
2975 new log("modify","users/passwordMethod",$dn,array(),"Password change - Post modify hook reported! - FAILED!");
2976 $message = sprintf(_("Post-event hook reported a problem: %s. Password change canceled!"),implode($output));
2977 $ldapRollback = TRUE;
2978 $preRollback = TRUE;
2979 $success = FALSE;
2980 }else{
2981 #new log("modify","users/passwordMethod",$dn,array(),"Password change - successfull!");
2982 }
2983 }else{
2984 $ldapRollback = TRUE;
2985 $preRollback = TRUE;
2986 $success = FALSE;
2987 new log("modify","users/passwordMethod",$dn,array(),"Password change - postmodify hook execution! - FAILED");
2988 new log("modify","users/passwordMethod",$dn,array(),$error);
2990 // Call password method again and send in old password to
2991 // keep the database consistency
2992 $test->set_password($old_password);
2993 }
2994 }
2995 }
2997 // Setting the password in the ldap database or further operation failed, we should now execute
2998 // the plugins pre-event hook, using switched passwords, new/old password.
2999 // This ensures that passwords which were set outside of GOsa, will be reset to its
3000 // starting value.
3001 if($preRollback){
3002 new log("modify","users/passwordMethod",$dn,array(),"Rolling back premodify hook!");
3003 $oldpass= $test->generate_hash($old_password);
3004 $attrsEvent['current_password'] = escapeshellarg($password);
3005 $attrsEvent['new_password'] = escapeshellarg($old_password);
3006 foreach(array("userPassword","sambaNTPassword","sambaLMPassword") as $attr){
3007 if(isset($initialAttrs[$attr][0])) $attrsEvent[$attr] = $initialAttrs[$attr][0];
3008 }
3010 plugin::callHook($passwordPlugin, 'PREMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
3011 if($retCode === 0 && count($output)){
3012 $message = sprintf(_("Pre-event hook reported a problem: %s. Password change canceled!"),implode($output));
3013 new log("modify","users/passwordMethod",$dn,array(),"Rolling back premodify hook! - FAILED!");
3014 }
3015 }
3017 // We've written the password to the ldap database, but executing the postmodify hook failed.
3018 // Now, we've to rollback all password related ldap operations.
3019 if($ldapRollback){
3020 new log("modify","users/passwordMethod",$dn,array(),"Rolling back ldap modifications!");
3021 $attrs = array();
3022 foreach(array("userPassword","sambaNTPassword","sambaLMPassword") as $attr){
3023 if(isset($initialAttrs[$attr][0])) $attrs[$attr] = $initialAttrs[$attr][0];
3024 }
3025 $ldap->cd($dn);
3026 $ldap->modify($attrs);
3027 if(!$ldap->success()){
3028 $message = msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD);
3029 new log("modify","users/passwordMethod",$dn,array(),"Rolling back ldap modifications! - FAILED");
3030 }
3031 }
3033 // Log action.
3034 if($success){
3035 stats::log('global', 'global', array('users'), $action = 'change_password', $amount = 1, 0, $test->get_hash());
3036 new log("modify","users/passwordMethod",$dn,array(),"Password change - successfull!");
3037 }else{
3038 new log("modify","users/passwordMethod",$dn,array(),"Password change - FAILED!");
3039 }
3041 return($success);
3042 }
3043 }
3046 /*! \brief Generate samba hashes
3047 *
3048 * Given a certain password this constructs an array like
3049 * array['sambaLMPassword'] etc.
3050 *
3051 * \param string 'password'
3052 * \return array contains several keys for lmPassword, ntPassword, pwdLastSet, etc. depending
3053 * on the samba version
3054 */
3055 function generate_smb_nt_hash($password)
3056 {
3057 global $config;
3059 // First try to retrieve values via RPC
3060 if ($config->get_cfg_value("core","gosaRpcServer") != ""){
3062 $rpc = $config->getRpcHandle();
3063 $hash = $rpc->mksmbhash($password);
3064 if(!$rpc->success()){
3065 msg_dialog::display(_("Error"),msgPool::rpcError($rpc->get_error()),ERROR_DIALOG);
3066 return("");
3067 }
3069 }elseif ($config->get_cfg_value("core","gosaSupportURI") != ""){
3071 // Try using gosa-si
3072 $res= gosaSupportDaemon::send("gosa_gen_smb_hash", "GOSA", array("password" => $password), TRUE);
3073 if (isset($res['XML']['HASH'])){
3074 $hash= $res['XML']['HASH'];
3075 } else {
3076 $hash= "";
3077 }
3079 if ($hash == "") {
3080 msg_dialog::display(_("Configuration error"), _("Cannot generate SAMBA hash!"), ERROR_DIALOG);
3081 return ("");
3082 }
3083 } else {
3084 $tmp = $config->get_cfg_value("core",'sambaHashHook');
3085 $tmp = preg_replace("/%userPassword/", escapeshellarg($password), $tmp);
3086 $tmp = preg_replace("/%password/", escapeshellarg($password), $tmp);
3087 @DEBUG (DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $tmp, "Execute");
3089 exec($tmp, $ar);
3090 flush();
3091 reset($ar);
3092 $hash= current($ar);
3094 if ($hash == "") {
3095 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);
3096 return ("");
3097 }
3098 }
3100 list($lm,$nt)= explode(":", trim($hash));
3102 $attrs['sambaLMPassword']= $lm;
3103 $attrs['sambaNTPassword']= $nt;
3104 $attrs['sambaPwdLastSet']= date('U');
3105 $attrs['sambaBadPasswordCount']= "0";
3106 $attrs['sambaBadPasswordTime']= "0";
3107 return($attrs);
3108 }
3111 /*! \brief Get the Change Sequence Number of a certain DN
3112 *
3113 * To verify if a given object has been changed outside of Gosa
3114 * in the meanwhile, this function can be used to get the entryCSN
3115 * from the LDAP directory. It uses the attribute as configured
3116 * in modificationDetectionAttribute
3117 *
3118 * \param string 'dn'
3119 * \return either the result or "" in any other case
3120 */
3121 function getEntryCSN($dn)
3122 {
3123 global $config;
3124 if(empty($dn) || !is_object($config)){
3125 return("");
3126 }
3128 /* Get attribute that we should use as serial number */
3129 $attr= $config->get_cfg_value("core","modificationDetectionAttribute");
3130 if($attr != ""){
3131 $ldap = $config->get_ldap_link();
3132 $ldap->cat($dn,array($attr));
3133 $csn = $ldap->fetch();
3134 if(isset($csn[$attr][0])){
3135 return($csn[$attr][0]);
3136 }
3137 }
3138 return("");
3139 }
3142 /*! \brief Add (a) given objectClass(es) to an attrs entry
3143 *
3144 * The function adds the specified objectClass(es) to the given
3145 * attrs entry.
3146 *
3147 * \param mixed 'classes' Either a single objectClass or several objectClasses
3148 * as an array
3149 * \param array 'attrs' The attrs array to be modified.
3150 *
3151 * */
3152 function add_objectClass($classes, &$attrs)
3153 {
3154 if (is_array($classes)){
3155 $list= $classes;
3156 } else {
3157 $list= array($classes);
3158 }
3160 foreach ($list as $class){
3161 $attrs['objectClass'][]= $class;
3162 }
3163 }
3166 /*! \brief Removes a given objectClass from the attrs entry
3167 *
3168 * Similar to add_objectClass, except that it removes the given
3169 * objectClasses. See it for the params.
3170 * */
3171 function remove_objectClass($classes, &$attrs)
3172 {
3173 if (isset($attrs['objectClass'])){
3174 /* Array? */
3175 if (is_array($classes)){
3176 $list= $classes;
3177 } else {
3178 $list= array($classes);
3179 }
3181 $tmp= array();
3182 foreach ($attrs['objectClass'] as $oc) {
3183 foreach ($list as $class){
3184 if (strtolower($oc) != strtolower($class)){
3185 $tmp[]= $oc;
3186 }
3187 }
3188 }
3189 $attrs['objectClass']= $tmp;
3190 }
3191 }
3194 /*! \brief Initialize a file download with given content, name and data type.
3195 * \param string data The content to send.
3196 * \param string name The name of the file.
3197 * \param string type The content identifier, default value is "application/octet-stream";
3198 */
3199 function send_binary_content($data,$name,$type = "application/octet-stream")
3200 {
3201 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
3202 header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
3203 header("Cache-Control: no-cache");
3204 header("Pragma: no-cache");
3205 header("Cache-Control: post-check=0, pre-check=0");
3206 header("Content-type: ".$type."");
3208 $HTTP_USER_AGENT = $_SERVER['HTTP_USER_AGENT'];
3210 /* Strip name if it is a complete path */
3211 if (preg_match ("/\//", $name)) {
3212 $name= basename($name);
3213 }
3215 /* force download dialog */
3216 if (preg_match('/MSIE 5.5/', $HTTP_USER_AGENT) || preg_match('/MSIE 6.0/', $HTTP_USER_AGENT)) {
3217 header('Content-Disposition: filename="'.$name.'"');
3218 } else {
3219 header('Content-Disposition: attachment; filename="'.$name.'"');
3220 }
3222 echo $data;
3223 exit();
3224 }
3227 function reverse_html_entities($str,$type = ENT_QUOTES , $charset = "UTF-8")
3228 {
3229 if(is_string($str)){
3230 return(htmlentities($str,$type,$charset));
3231 }elseif(is_array($str)){
3232 foreach($str as $name => $value){
3233 $str[$name] = reverse_html_entities($value,$type,$charset);
3234 }
3235 }
3236 return($str);
3237 }
3240 /*! \brief Encode special string characters so we can use the string in \
3241 HTML output, without breaking quotes.
3242 \param string The String we want to encode.
3243 \return string The encoded String
3244 */
3245 function xmlentities($str)
3246 {
3247 if(is_string($str)){
3249 static $asc2uni= array();
3250 if (!count($asc2uni)){
3251 for($i=128;$i<256;$i++){
3252 # $asc2uni[chr($i)] = "&#x".dechex($i).";";
3253 }
3254 }
3256 $str = str_replace("&", "&", $str);
3257 $str = str_replace("<", "<", $str);
3258 $str = str_replace(">", ">", $str);
3259 $str = str_replace("'", "'", $str);
3260 $str = str_replace("\"", """, $str);
3261 $str = str_replace("\r", "", $str);
3262 $str = strtr($str,$asc2uni);
3263 return $str;
3264 }elseif(is_array($str)){
3265 foreach($str as $name => $value){
3266 $str[$name] = xmlentities($value);
3267 }
3268 }
3269 return($str);
3270 }
3273 /*! \brief Updates all accessTo attributes from a given value to a new one.
3274 For example if a host is renamed.
3275 \param String $from The source accessTo name.
3276 \param String $to The destination accessTo name.
3277 */
3278 function update_accessTo($from,$to)
3279 {
3280 global $config;
3281 $ldap = $config->get_ldap_link();
3282 $ldap->cd($config->current['BASE']);
3283 $ldap->search("(&(objectClass=trustAccount)(accessTo=".$from."))",array("objectClass","accessTo"));
3284 while($attrs = $ldap->fetch()){
3285 $new_attrs = array("accessTo" => array());
3286 $dn = $attrs['dn'];
3287 for($i = 0 ; $i < $attrs['accessTo']['count']; $i++){
3288 if($attrs['accessTo'][$i] == $from){
3289 if(!empty($to)){
3290 $new_attrs['accessTo'][] = $to;
3291 }
3292 }else{
3293 $new_attrs['accessTo'][] = $attrs['accessTo'][$i];
3294 }
3295 }
3296 $ldap->cd($dn);
3297 $ldap->modify($new_attrs);
3298 if (!$ldap->success()){
3299 msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD, "update_accessTo($from,$to)"));
3300 }
3301 new log("modify","update_accessTo($from,$to)",$dn,array_keys($new_attrs),$ldap->get_error());
3302 }
3303 }
3306 /*! \brief Returns a random char */
3307 function get_random_char () {
3308 $randno = rand (0, 63);
3309 if ($randno < 12) {
3310 return (chr ($randno + 46)); // Digits, '/' and '.'
3311 } else if ($randno < 38) {
3312 return (chr ($randno + 53)); // Uppercase
3313 } else {
3314 return (chr ($randno + 59)); // Lowercase
3315 }
3316 }
3319 function cred_encrypt($input, $password) {
3321 $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
3322 $iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
3324 return bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $password, $input, MCRYPT_MODE_ECB, $iv));
3326 }
3329 function cred_decrypt($input,$password) {
3330 $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
3331 $iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
3333 return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, pack("H*", $input), MCRYPT_MODE_ECB, $iv);
3334 }
3337 function get_object_info()
3338 {
3339 return(session::get('objectinfo'));
3340 }
3343 function set_object_info($str = "")
3344 {
3345 session::set('objectinfo',$str);
3346 }
3349 function isIpInNet($ip, $net, $mask) {
3350 // Move to long ints
3351 $ip= ip2long($ip);
3352 $net= ip2long($net);
3353 $mask= ip2long($mask);
3355 // Mask given IP with mask. If it returns "net", we're in...
3356 $res= $ip & $mask;
3358 return ($res == $net);
3359 }
3362 function get_next_id($attrib, $dn)
3363 {
3364 global $config;
3366 switch ($config->get_cfg_value("core","idAllocationMethod")){
3367 case "pool":
3368 return get_next_id_pool($attrib);
3369 case "traditional":
3370 return get_next_id_traditional($attrib, $dn);
3371 }
3373 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("unknown idAllocation method!"), ERROR_DIALOG);
3374 return null;
3375 }
3378 function get_next_id_pool($attrib) {
3379 global $config;
3381 /* Fill informational values */
3382 $min= $config->get_cfg_value("core","${attrib}PoolMin");
3383 $max= $config->get_cfg_value("core","${attrib}PoolMax");
3385 /* Sanity check */
3386 if ($min >= $max) {
3387 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." ".sprintf(_("%sPoolMin >= %sPoolMax!"), bold($attrib), bold($attrib)), ERROR_DIALOG);
3388 return null;
3389 }
3391 /* ID to skip */
3392 $ldap= $config->get_ldap_link();
3393 $id= null;
3395 /* Try to allocate the ID several times before failing */
3396 $tries= 3;
3397 while ($tries--) {
3399 /* Look for ID map entry */
3400 $ldap->cd ($config->current['BASE']);
3401 $ldap->search ("(&(objectClass=sambaUnixIdPool)($attrib=*))", array("$attrib"));
3403 /* If it does not exist, create one with these defaults */
3404 if ($ldap->count() == 0) {
3405 /* Fill informational values */
3406 $minUserId= $config->get_cfg_value("core","uidNumberPoolMin");
3407 $minGroupId= $config->get_cfg_value("core","gidNumberPoolMin");
3409 /* Add as default */
3410 $attrs= array("objectClass" => array("organizationalUnit", "sambaUnixIdPool"));
3411 $attrs["ou"]= "idmap";
3412 $attrs["uidNumber"]= $minUserId;
3413 $attrs["gidNumber"]= $minGroupId;
3414 $ldap->cd("ou=idmap,".$config->current['BASE']);
3415 $ldap->add($attrs);
3416 if ($ldap->error != "Success") {
3417 msg_dialog::display(_("Error"), _("Cannot create sambaUnixIdPool entry!"), ERROR_DIALOG);
3418 return null;
3419 }
3420 $tries++;
3421 continue;
3422 }
3423 /* Bail out if it's not unique */
3424 if ($ldap->count() != 1) {
3425 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("sambaUnixIdPool is not unique!"), ERROR_DIALOG);
3426 return null;
3427 }
3429 /* Store old attrib and generate new */
3430 $attrs= $ldap->fetch();
3431 $dn= $ldap->getDN();
3432 $oldAttr= $attrs[$attrib][0];
3433 $newAttr= $oldAttr + 1;
3435 /* Sanity check */
3436 if ($newAttr >= $max) {
3437 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("no ID available!"), ERROR_DIALOG);
3438 return null;
3439 }
3440 if ($newAttr < $min) {
3441 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("no ID available!"), ERROR_DIALOG);
3442 return null;
3443 }
3445 #FIXME: PHP is not able to do a modification of "del: .../add: ...", so this
3446 # is completely unsafe in the moment.
3447 #/* Remove old attr, add new attr */
3448 #$attrs= array($attrib => $oldAttr);
3449 #$ldap->rm($attrs, $dn);
3450 #if ($ldap->error != "Success") {
3451 # continue;
3452 #}
3453 $ldap->cd($dn);
3454 $ldap->modify(array($attrib => $newAttr));
3455 if ($ldap->error != "Success") {
3456 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." ".$ldap->get_error(), ERROR_DIALOG);
3457 return null;
3458 } else {
3459 return $oldAttr;
3460 }
3461 }
3463 /* Bail out if we had problems getting the next id */
3464 if (!$tries) {
3465 msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("maximum number of tries exceeded!"), ERROR_DIALOG);
3466 }
3468 return $id;
3469 }
3472 function get_next_id_traditional($attrib, $dn)
3473 {
3474 global $config;
3476 $ids= array();
3477 $ldap= $config->get_ldap_link();
3479 $ldap->cd ($config->current['BASE']);
3480 if (preg_match('/gidNumber/i', $attrib)){
3481 $oc= "posixGroup";
3482 } else {
3483 $oc= "posixAccount";
3484 }
3485 $ldap->search ("(&(objectClass=$oc)($attrib=*))", array("$attrib"));
3487 /* Get list of ids */
3488 while ($attrs= $ldap->fetch()){
3489 $ids[]= (int)$attrs["$attrib"][0];
3490 }
3492 /* Add the nobody id */
3493 $ids[]= 65534;
3495 /* get the ranges */
3496 $tmp = array('0'=> 1000);
3497 if (preg_match('/posixAccount/', $oc) && $config->get_cfg_value("core","uidNumberBase") != ""){
3498 $tmp= explode('-',$config->get_cfg_value("core","uidNumberBase"));
3499 } elseif($config->get_cfg_value("core","gidNumberBase") != ""){
3500 $tmp= explode('-',$config->get_cfg_value("core","gidNumberBase"));
3501 }
3503 /* Set hwm to max if not set - for backward compatibility */
3504 $lwm= $tmp[0];
3505 if (isset($tmp[1])){
3506 $hwm= $tmp[1];
3507 } else {
3508 $hwm= pow(2,32);
3509 }
3510 /* Find out next free id near to UID_BASE */
3511 if ($config->get_cfg_value("core","baseIdHook") == ""){
3512 $base= $lwm;
3513 } else {
3514 /* Call base hook */
3515 $base= get_base_from_hook($dn, $attrib);
3516 }
3517 for ($id= $base; $id++; $id < pow(2,32)){
3518 if (!in_array($id, $ids)){
3519 return ($id);
3520 }
3521 }
3523 /* Should not happen */
3524 if ($id == $hwm){
3525 msg_dialog::display(_("Error"), _("Cannot allocate free ID!"), ERROR_DIALOG);
3526 exit;
3527 }
3528 }
3531 /* Mark the occurance of a string with a span */
3532 function mark($needle, $haystack, $ignorecase= true)
3533 {
3534 $result= "";
3536 while (preg_match('/^(.*)('.preg_quote($needle).')(.*)$/i', $haystack, $matches)) {
3537 $result.= $matches[1]."<span class='mark'>".$matches[2]."</span>";
3538 $haystack= $matches[3];
3539 }
3541 return $result.$haystack;
3542 }
3545 /* Return an image description using the path */
3546 function image($path, $action= "", $title= "", $align= "middle")
3547 {
3548 global $config;
3549 global $BASE_DIR;
3550 $label= null;
3552 // Bail out, if there's no style file
3553 if(!session::global_is_set("img-styles")){
3555 // Get theme
3556 if (isset ($config)){
3557 $theme= $config->get_cfg_value("core","theme");
3558 } else {
3560 // Fall back to default theme
3561 $theme= "default";
3562 }
3564 if (!file_exists("$BASE_DIR/ihtml/themes/$theme/img.styles")){
3565 die ("No img.style for this theme found!");
3566 }
3568 session::global_set('img-styles', unserialize(file_get_contents("$BASE_DIR/ihtml/themes/$theme/img.styles")));
3569 }
3570 $styles= session::global_get('img-styles');
3572 /* Extract labels from path */
3573 if (preg_match("/\.png\[(.*)\]$/", $path, $matches)) {
3574 $label= $matches[1];
3575 }
3577 $lbl= "";
3578 if ($label) {
3579 if (isset($styles["images/label-".$label.".png"])) {
3580 $lbl= "<div style='".$styles["images/label-".$label.".png"]."'></div>";
3581 } else {
3582 die("Invalid label specified: $label\n");
3583 }
3585 $path= preg_replace("/\[.*\]$/", "", $path);
3586 }
3588 // Non middle layout?
3589 if ($align == "middle") {
3590 $align= "";
3591 } else {
3592 $align= ";vertical-align:$align";
3593 }
3595 // Clickable image or not?
3596 if ($title != "") {
3597 $title= "title='$title'";
3598 }
3599 if ($action == "") {
3600 return "<div class='img' $title style='".$styles[$path]."$align'>$lbl</div>";
3601 } else {
3602 return "<input type='submit' class='img' id='$action' value='' name='$action' $title style='".$styles[$path]."$align'>";
3603 }
3604 }
3606 /*! \brief Encodes a complex string to be useable in HTML posts.
3607 */
3608 function postEncode($str)
3609 {
3610 return(preg_replace("/=/","_", base64_encode($str)));
3611 }
3613 /*! \brief Decodes a string encoded by postEncode
3614 */
3615 function postDecode($str)
3616 {
3617 return(base64_decode(preg_replace("/_/","=", $str)));
3618 }
3621 /*! \brief Generate styled output
3622 */
3623 function bold($str)
3624 {
3625 return "<span class='highlight'>$str</span>";
3626 }
3630 /*! \brief Detect the special character handling for the currently used ldap database.
3631 * For example some convert , to \2C or " to \22.
3632 *
3633 * @param Config The GOsa configuration object.
3634 * @return Array An array containing a character mapping the use.
3635 */
3636 function detectLdapSpecialCharHandling()
3637 {
3638 // The list of chars to test for
3639 global $config;
3640 if(!$config) return(NULL);
3642 // In the DN we've to use escaped characters, but the object name (o)
3643 // has the be un-escaped.
3644 $name = 'GOsaLdapEncoding_,_"_(_)_+_/';
3645 $dnName = 'GOsaLdapEncoding_\,_\"_(_)_\+_/';
3647 // Prapare name to be useable in filters
3648 $fixed= normalizeLdap(str_replace('\\\\', '\\\\\\', $name));
3649 $filterName = str_replace('\\,', '\\\\,', $fixed);
3651 // Create the target dn
3652 $oDN = "o={$dnName},".$config->current['BASE'];
3654 // Get ldap connection and check if we've already created the character
3655 // detection object.
3656 $ldapCID = ldap_connect($config->current['SERVER']);
3657 ldap_set_option($ldapCID, LDAP_OPT_PROTOCOL_VERSION, 3);
3658 ldap_bind($ldapCID, $config->current['ADMINDN'],$config->current['ADMINPASSWORD']);
3659 $res = ldap_list($ldapCID, $config->current['BASE'],
3660 "(&(o=".$filterName.")(objectClass=organization))",
3661 array('dn'));
3663 // If we haven't created the character-detection object, then create it now.
3664 $cnt = ldap_count_entries($ldapCID, $res);
3665 if(!$cnt){
3666 $obj = array();
3667 $obj['objectClass'] = array('top','organization');
3668 $obj['o'] = $name;
3669 $obj['description'] = 'GOsa character encoding test-object.';
3670 if(!@ldap_add($ldapCID, $oDN, $obj)){
3671 trigger_error("GOsa couldn't detect the special character handling used by your ldap!");
3672 return(NULL);
3673 }
3674 }
3676 // Read the character-handling detection entry from the ldap.
3677 $res = ldap_list($ldapCID, $config->current['BASE'],
3678 "(&(o=".$filterName.")(objectClass=organization))",
3679 array('dn','o'));
3680 $cnt = ldap_count_entries($ldapCID, $res);
3681 if($cnt != 1 || !$res){
3682 trigger_error("GOsa couldn't detect the special character handling used by your ldap!");
3683 return(NULL);
3684 }else{
3686 // Get the character handling entry from the ldap and check how the
3687 // values were written. Compare them with what
3688 // we've initially intended to write and create a mapping out
3689 // of the results.
3690 $re = ldap_first_entry($ldapCID, $res);
3691 $attrs = ldap_get_attributes($ldapCID, $re);
3693 // Extract the interessting characters out of the dn and the
3694 // initially used $name for the entry.
3695 $mapDNstr = preg_replace("/^o=GOsaLdapEncoding_([^,]*),.*$/","\\1", trim(ldap_get_dn($ldapCID, $re)));
3696 $mapDN = preg_split("/_/", $mapDNstr,0, PREG_SPLIT_NO_EMPTY);
3698 $mapNameStr = preg_replace("/^GOsaLdapEncoding_/","",$dnName);
3699 $mapName = preg_split("/_/", $mapNameStr,0, PREG_SPLIT_NO_EMPTY);
3701 // Create a mapping out of the results.
3702 $map = array();
3703 foreach($mapName as $key => $entry){
3704 $map[$entry] = $mapDN[$key];
3705 }
3706 return($map);
3707 }
3708 return(NULL);
3709 }
3712 /*! \brief Replaces placeholder in a given string.
3713 * For example:
3714 * '%uid@gonicus.de' Replaces '%uid' with 'uid'.
3715 * '{%uid[0]@gonicus.de}' Replaces '%uid[0]' with the first char of 'uid'.
3716 * '%uid[2-4]@gonicus.de' Replaces '%uid[2-4]' with three chars from 'uid' starting from the second.
3717 *
3718 * The surrounding {} in example 2 are optional.
3719 *
3720 * @param String The string to perform the action on.
3721 * @param Array An array of replacements.
3722 * @return The resulting string.
3723 */
3724 function fillReplacements($str, $attrs, $shellArg = FALSE, $default = "")
3725 {
3726 // Search for '{%...[n-m]}
3727 // Get all matching parts of the given string and sort them by
3728 // length, to avoid replacing strings like '%uidNumber' with 'uid'
3729 // instead of 'uidNumber'; The longest tring at first.
3730 preg_match_all('/(\{?%([a-z0-9_]+)(\[(([0-9_]+)(\-([0-9_]+))?)\])?\}?)/i', $str ,$matches, PREG_SET_ORDER);
3731 $hits = array();
3732 foreach($matches as $match){
3733 $hits[strlen($match[2]).$match[0]] = $match;
3734 }
3735 krsort($hits);
3737 // Add lower case placeholders to avoid errors
3738 foreach($attrs as $key => $attr) $attrs[strtolower($key)] = $attr;
3740 // Replace the placeholder in the given string now.
3741 foreach($hits as $match){
3743 // Avoid errors about undefined index.
3744 $name = strtolower($match[2]);
3745 if(!isset($attrs[$name])) $attrs[$name] = $default;
3747 // Calculate the replacement
3748 $start = (isset($match[5])) ? $match[5] : 0;
3749 $end = strlen($attrs[$name]);
3750 if(isset($match[5]) && !isset($match[7])){
3751 $end = 1;
3752 }elseif(isset($match[5]) && isset($match[7])){
3753 $end = ($match[7]-$start+1);
3754 }
3755 $value = substr($attrs[$name], $start, $end);
3757 // Use values which are valid for shell execution?
3758 if($shellArg) $value = escapeshellarg($value);
3760 // Replace the placeholder within the string.
3761 $str = preg_replace("/".preg_quote($match[0],'/')."/", $value, $str);
3762 }
3763 return($str);
3764 }
3767 /*! \brief Generate a list of uid proposals based on a rule
3768 *
3769 * Unroll given rule string by filling in attributes and replacing
3770 * all keywords.
3771 *
3772 * \param string 'rule' The rule string from gosa.conf.
3773 * \param array 'attributes' A dictionary of attribute/value mappings
3774 * \return array List of valid not used uids
3775 */
3776 function gen_uids($rule, $attributes)
3777 {
3778 global $config;
3779 $ldap = $config->get_ldap_link();
3780 $ldap->cd($config->current['BASE']);
3782 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $rule, "Processing");
3784 // Strip out non ascii chars
3785 foreach($attributes as $name => $value){
3786 if ( $config->get_cfg_value("core", "forceTranslit") == "true" ) {
3787 $value = cyrillic2ascii($value);
3788 } else {
3789 $value = iconv('UTF-8', 'US-ASCII//TRANSLIT', $value);
3790 }
3791 $value = preg_replace('/[^(\x20-\x7F)]*/','',$value);
3792 $attributes[$name] = strtolower($value);
3793 }
3795 @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $attributes, "Prepare");
3797 // Search for '{%...[n-m]}
3798 // Get all matching parts of the given string and sort them by
3799 // length, to avoid replacing strings like '%uidNumber' with 'uid'
3800 // instead of 'uidNumber'; The longest tring at first.
3801 preg_match_all('/(\{?%([a-z0-9]+)(\[(([0-9]+)(\-([0-9]+))?)\])?\}?)/i', $rule ,$matches, PREG_SET_ORDER);
3802 $replacements = array();
3803 foreach($matches as $match){
3805 // No start position given, then add the complete value
3806 if(!isset($match[5])){
3807 $replacements[$match[0]][] = $attributes[$match[2]];
3809 // Start given but no end, so just add a single character
3810 }elseif(!isset($match[7])){
3811 if(isset($attributes[$match[2]][$match[5]])){
3812 $tmp = " ".$attributes[$match[2]];
3813 $replacements[$match[0]][] = trim($tmp[$match[5]]);
3814 }
3816 // Add all values in range
3817 }else{
3818 $str = "";
3819 for($i=$match[5]; $i<= $match[7]; $i++){
3820 if(isset($attributes[$match[2]][$i])){
3821 $tmp = " ".$attributes[$match[2]];
3822 $str .= $tmp[$i];
3823 $replacements[$match[0]][] = trim($str);
3824 }
3825 }
3826 }
3827 }
3829 // Create proposal array
3830 $rules = array($rule);
3831 foreach($replacements as $tag => $values){
3832 $rules = gen_uid_proposals($rules, $tag, $values);
3833 }
3836 // Search for id tags {id:3} / {id#3}
3837 preg_match_all('/\{id(#|:)([0-9])+\}/i', $rule, $matches, PREG_SET_ORDER);
3838 $idReplacements = array();
3839 foreach($matches as $match){
3840 if(count($match) != 3) continue;
3842 // Generate random number
3843 if($match[1] == '#'){
3844 foreach($rules as $id => $ruleStr){
3845 $genID = rand(pow(10,$match[2] -1),pow(10, ($match[2])) - 1);
3846 $rules[$id] = preg_replace("/".preg_quote($match[0],'/')."/", $genID,$ruleStr);
3847 }
3848 }
3850 // Search for next free id
3851 if($match[1] == ':'){
3853 // Walk through rules and replace all occurences of {id:..}
3854 foreach($rules as $id => $ruleStr){
3855 $genID = 0;
3856 $start = TRUE;
3857 while($start || $ldap->count()){
3858 $start = FALSE;
3859 $number= sprintf("%0".$match[2]."d", $genID);
3860 $testRule = preg_replace("/".preg_quote($match[0],'/')."/",$number,$ruleStr);
3861 $ldap->search('uid='.normalizeLdap($testRule));
3862 $genID ++;
3863 }
3864 $rules[$id] = preg_replace("/".preg_quote($match[0],'/')."/",$number,$ruleStr);
3865 }
3866 }
3867 }
3869 // Create result set by checking which uid is already used and which is free.
3870 $ret = array();
3871 foreach($rules as $rule){
3872 $ldap->search('uid='.normalizeLdap($rule));
3873 if(!$ldap->count()){
3874 $ret[] = $rule;
3875 }
3876 }
3878 return($ret);
3879 }
3882 function gen_uid_proposals(&$rules, $tag, $values)
3883 {
3884 $newRules = array();
3885 foreach($rules as $rule){
3886 foreach($values as $value){
3887 $newRules[] = preg_replace("/".preg_quote($tag,'/')."/", $value, $rule);
3888 }
3889 }
3890 return($newRules);
3891 }
3894 function gen_uuid()
3895 {
3896 return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
3897 // 32 bits for "time_low"
3898 mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
3900 // 16 bits for "time_mid"
3901 mt_rand( 0, 0xffff ),
3903 // 16 bits for "time_hi_and_version",
3904 // four most significant bits holds version number 4
3905 mt_rand( 0, 0x0fff ) | 0x4000,
3907 // 16 bits, 8 bits for "clk_seq_hi_res",
3908 // 8 bits for "clk_seq_low",
3909 // two most significant bits holds zero and one for variant DCE1.1
3910 mt_rand( 0, 0x3fff ) | 0x8000,
3912 // 48 bits for "node"
3913 mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
3914 );
3915 }
3917 function gosa_file_name($filename)
3918 {
3919 $tempfile = tempnam(sys_get_temp_dir(), 'GOsa');
3920 if(move_uploaded_file($filename, $tempfile)){
3921 return( $tempfile);
3922 }
3923 }
3925 function gosa_file($filename)
3926 {
3927 $tempfile = tempnam(sys_get_temp_dir(), 'GOsa');
3928 if(move_uploaded_file($filename, $tempfile)){
3929 return file( $tempfile );
3930 }
3931 }
3933 function gosa_fopen($filename, $mode)
3934 {
3935 $tempfile = tempnam(sys_get_temp_dir(), 'GOsa');
3936 if(move_uploaded_file($filename, $tempfile)){
3937 return fopen( $tempfile, $mode );
3938 }
3939 }
3941 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
3942 ?>