Code

Removed accidentially commited tr method
[gosa.git] / gosa-core / include / functions.inc
1 <?php
2 /*
3  * This code is part of GOsa (http://www.gosa-project.org)
4  * Copyright (C) 2003-2008 GONICUS GmbH
5  *
6  * ID: $$Id: functions.inc 13100 2008-12-01 14:07:48Z hickert $$
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
23 /*! \file
24  * Common functions and named definitions. */
26 /* Define globals for revision comparing */
27 $svn_path = '$HeadURL$';
28 $svn_revision = '$Revision$';
30 /* Configuration file location */
31 if(!isset($_SERVER['CONFIG_DIR'])){
32   define ("CONFIG_DIR", "/etc/gosa");
33 }else{
34   define ("CONFIG_DIR",$_SERVER['CONFIG_DIR']);
35 }
37 /* Allow setting the config file in the apache configuration
38     e.g.  SetEnv CONFIG_FILE gosa.conf.2.6
39  */
40 if(!isset($_SERVER['CONFIG_FILE'])){
41   define ("CONFIG_FILE", "gosa.conf");
42 }else{
43   define ("CONFIG_FILE",$_SERVER['CONFIG_FILE']);
44 }
46 /* Define common locatitions */
47 define ("CONFIG_TEMPLATE_DIR", "../contrib");
48 define ("TEMP_DIR","/var/cache/gosa/tmp");
50 /* Define get_list flags */
51 define("GL_NONE",         0);
52 define("GL_SUBSEARCH",    1);
53 define("GL_SIZELIMIT",    2);
54 define("GL_CONVERT",      4);
55 define("GL_NO_ACL_CHECK", 8);
57 /* Heimdal stuff */
58 define('UNIVERSAL',0x00);
59 define('INTEGER',0x02);
60 define('OCTET_STRING',0x04);
61 define('OBJECT_IDENTIFIER ',0x06);
62 define('SEQUENCE',0x10);
63 define('SEQUENCE_OF',0x10);
64 define('SET',0x11);
65 define('SET_OF',0x11);
66 define('DEBUG',false);
67 define('HDB_KU_MKEY',0x484442);
68 define('TWO_BIT_SHIFTS',0x7efc);
69 define('DES_CBC_CRC',1);
70 define('DES_CBC_MD4',2);
71 define('DES_CBC_MD5',3);
72 define('DES3_CBC_MD5',5);
73 define('DES3_CBC_SHA1',16);
75 /* Include required files */
76 include_once("class_location.inc");
77 require_once ("functions_debug.inc");
78 require_once ("accept-to-gettext.inc");
80 /* Define constants for debugging */
81 define ("DEBUG_TRACE",   1); /*! Debug level for tracing of common actions (save, check, etc.) */
82 define ("DEBUG_LDAP",    2); /*! Debug level for LDAP queries */
83 define ("DEBUG_MYSQL",   4); /*! Debug level for mysql operations */
84 define ("DEBUG_SHELL",   8); /*! Debug level for shell commands */
85 define ("DEBUG_POST",   16); /*! Debug level for POST content */
86 define ("DEBUG_SESSION",32); /*! Debug level for SESSION content */
87 define ("DEBUG_CONFIG", 64); /*! Debug level for CONFIG information */
88 define ("DEBUG_ACL",    128); /*! Debug level for ACL infos */
89 define ("DEBUG_SI",     256); /*! Debug level for communication with gosa-si */
90 define ("DEBUG_MAIL",   512); /*! Debug level for all about mail (mailAccounts, imap, sieve etc.) */
91 define ("DEBUG_FAI",   1024); // FAI (incomplete)
92 define ("DEBUG_RPC",   2048); /*! Debug level for communication with remote procedures */
94 // Define shadow states
95 define ("POSIX_ACCOUNT_EXPIRED", 1);
96 define ("POSIX_WARN_ABOUT_EXPIRATION", 2);
97 define ("POSIX_FORCE_PASSWORD_CHANGE", 4);
98 define ("POSIX_DISALLOW_PASSWORD_CHANGE", 8);
100 /* Rewrite german 'umlauts' and spanish 'accents'
101    to get better results */
102 $REWRITE= array( "ä" => "ae",
103     "ö" => "oe",
104     "ü" => "ue",
105     "Ä" => "Ae",
106     "Ö" => "Oe",
107     "Ü" => "Ue",
108     "ß" => "ss",
109     "á" => "a",
110     "é" => "e",
111     "í" => "i",
112     "ó" => "o",
113     "ú" => "u",
114     "Á" => "A",
115     "É" => "E",
116     "Í" => "I",
117     "Ó" => "O",
118     "Ú" => "U",
119     "ñ" => "ny",
120     "Ñ" => "Ny" );
122 /*! \brief Cyrillic (russian) fonetic transliteration (converts russian letters to ASCII and backward according to GOST 7.79-2000 )
123  *  \param  string 'str' Source string in russian codepage
124  *  \return string Translitered string value.
125  */
126 function cyrillic2ascii($str) {
127     $ru = array('а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ',  'ъ', 'ы', 'ь', 'э', 'ю', 'я',
128                 'А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'H', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ',  'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я', 
129                 'ґ', 'є', 'ї', 'Ґ', 'Є', 'Ї'
130     );
131     $en = array('a', 'b', 'v', 'g', 'd', 'e', 'jo','zh','z', 'i', 'jj','k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'f', 'kh','c', 'ch','sh','shh','"', 'y', '\'','eh','ju','ja',
132                 'A', 'B', 'V', 'G', 'D', 'E', 'Jo','Je','Z', 'I', 'Jj','K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'F', 'Kh','C', 'CH','SH','Shh','"', 'Y', '\'','Eh','Ju','Ja',
133                 'g', 'ye','yi','G', 'Ye','Yi'
134     );
136     return str_replace($ru, $en, $str);
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     }
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)
171   global $class_mapping, $config;
172     
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]));
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)
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         }
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);
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="")
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     }
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()
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   }
290  
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'));
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)
316   $ui= session::global_get('ui');
317   if ($ui->dn == $dn){
318     $ui->dn= $newdn;
319     session::global_set('ui',$ui);
320   }
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= "")
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   }
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)
402   return (array_merge(array_diff($haystack, $needles)));
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)
411   // strcasecmp will work, because we only compare ASCII values here
412   return (array_merge(array_udiff($haystack, $needles, 'strcasecmp')));
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)
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   }
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)
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");
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='')
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;
494 /* \brief Process htaccess authentication */
495 function process_htaccess ($username, $kerberos= FALSE)
497   global $config;
499   /* Search for $username and optional @REALM in all configured LDAP trees */
500   foreach($config->data["LOCATIONS"] as $name => $data){
501   
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" => "");
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)
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);
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)
595   global $config;
597   /* look through the entire ldap */
598   $ldap = $config->get_ldap_link();
599   if (!$ldap->success()){
600     msg_dialog::display(_("LDAP error"), 
601         msgPool::ldaperror($ldap->get_error(), "", LDAP_AUTH)."<br><br>".session::get('errors'), 
602         FATAL_ERROR_DIALOG);
603     exit();
604   }
605   $ldap->cd($config->current['BASE']);
606   $allowed_attributes = array("uid","mail");
607   $verify_attr = array();
608   if($config->get_cfg_value("core","loginAttribute") != ""){
609     $tmp = explode(",", $config->get_cfg_value("core","loginAttribute")); 
610     foreach($tmp as $attr){
611       if(in_array_strict($attr,$allowed_attributes)){
612         $verify_attr[] = $attr;
613       }
614     }
615   }
616   if(count($verify_attr) == 0){
617     $verify_attr = array("uid");
618   }
619   $tmp= $verify_attr;
620   $tmp[] = "uid";
621   $filter = "";
622   foreach($verify_attr as $attr) {
623     $filter.= "(".$attr."=".$username.")";
624   }
625   $filter = "(&(|".$filter.")(objectClass=gosaAccount))";
626   $ldap->search($filter,$tmp);
628   /* get results, only a count of 1 is valid */
629   switch ($ldap->count()){
631     /* user not found */
632     case 0:     return (NULL);
634             /* valid uniq user */
635     case 1: 
636             break;
638             /* found more than one matching id */
639     default:
640             msg_dialog::display(_("Internal error"), _("User ID is not unique!"), FATAL_ERROR_DIALOG);
641             return (NULL);
642   }
644   /* LDAP schema is not case sensitive. Perform additional check. */
645   $attrs= $ldap->fetch();
646   $success = FALSE;
647   foreach($verify_attr as $attr){
648     if(isset($attrs[$attr][0]) && $attrs[$attr][0] == $username){
649       $success = TRUE;
650     }
651   }
652   if(!$success){
653     return(FALSE);
654   }
656   /* got user dn, fill acl's */
657   $ui= new userinfo($config, $ldap->getDN());
658   $ui->username= $attrs['uid'][0];
660   /* Bail out if we have login restrictions set, for security reasons
661      the message is the same than failed user/pw */
662   if (!$ui->loginAllowed()){
663     new log("security","login","",array(),"Login restriction for user \"$username\", login not permitted");
664     return (NULL);
665   }
667   /* password check, bind as user with supplied password  */
668   $ldap->disconnect();
669   $ldap= new LDAP($ui->dn, $password, $config->current['SERVER'],
670       isset($config->current['LDAPFOLLOWREFERRALS']) &&
671       $config->current['LDAPFOLLOWREFERRALS'] == "true",
672       isset($config->current['LDAPTLS'])
673       && $config->current['LDAPTLS'] == "true");
674   if (!$ldap->success()){
675     return (NULL);
676   }
678   /* Username is set, load subtreeACL's now */
679   $ui->loadACL();
681   return ($ui);
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)
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);
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)
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   }
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)
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   }
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)
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   }
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)
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);
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)
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);
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)
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{
1095         
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   }
1105   
1106    /* If there is no sub_department specified, fall back to old method, get_list().
1107    */
1108   if(!count($sub_bases) && !count($departments)){
1109     
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);
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)
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   }
1275  
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);
1283 /*! \brief Show sizelimit configuration dialog if exceeded */
1284 function check_sizelimit()
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 ("");
1303 /*! \brief Print a sizelimit warning */
1304 function print_sizelimit_warning()
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 ("");
1319 /*! \brief Handle sizelimit dialog related posts */
1320 function eval_sizelimit()
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   }
1351 function getMenuCache()
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   }
1374 /*! \brief Return the current userinfo object */
1375 function &get_userinfo()
1377   global $ui;
1379   return $ui;
1383 /*! \brief Get global smarty object */
1384 function &get_smarty()
1386   global $smarty;
1388   return $smarty;
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)
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, "/"));
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)
1448   return (LDAP::fix(preg_replace("%^.*/([^/]+)/?$%", "\\1", $value)));
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)
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     }
1500 /*! \brief Get the OU for users 
1501  *
1502  * Frontend for get_ou() with userRDN
1503  * */
1504 function get_people_ou()
1506   return (get_ou("core", "userRDN"));
1510 /*! \brief Get the OU for groups
1511  *
1512  * Frontend for get_ou() with groupRDN
1513  */
1514 function get_groups_ou()
1516   return (get_ou("core", "groupRDN"));
1520 /*! \brief Get the OU for winstations
1521  *
1522  * Frontend for get_ou() with sambaMachineAccountRDN
1523  */
1524 function get_winstations_ou()
1526   return (get_ou("wingeneric", "sambaMachineAccountRDN"));
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)
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);
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()
1565   global $config;
1567   if (isset($config)){
1568     return ($config->get_cfg_value("core","strictNamingRules") == "true");
1569   }
1570   return (TRUE);
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()
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   }
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)
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')));
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)
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   }
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()
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;
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)
1735   global $REWRITE;
1737   foreach ($REWRITE as $key => $val){
1738     $s= str_replace("$key", "$val", $s);
1739   }
1741   return ($s);
1745 /*! \brief Return the base of a given DN
1746  *
1747  * \param string 'dn' a DN
1748  * */
1749 function dn2base($dn)
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);
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)
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);
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= "")
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=\"*\">&nbsp;$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.= "&nbsp;";
1811     $display.= "</div>\n";
1812   }
1813   return ($display);
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)
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'])."&amp;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'])."&amp;start=".
1894         ($i*$range)."\">&nbsp;".($i+1)."&nbsp;</a>";
1895     } else {
1896       $output.= "<a style=\"vertical-align:middle;\" href=\"main.php?plug=".validate($_GET['plug']).
1897         "&amp;start=".($i*$range)."\">&nbsp;".($i+1)."&nbsp;</a>";
1898     }
1899   }
1901   /* Draw increment */
1902   if($start < ($dcnt-$range)) {
1903     $output.="  <a href= \"main.php?plug=".validate($_GET['plug'])."&amp;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;'>&nbsp;"._("Entries per page")."&nbsp;<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);
1932 /*! \brief Generate HTML for the 'Back' button */
1933 function back_to_main()
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);
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)
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);
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)
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);
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   }
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)
2058         return preg_grep('/^'.preg_quote($value, '/').'$/i', $items);
2062 /*! \brief Removes malicious characters from a (POST) string. */
2063 function validate($string)
2065   return (strip_tags(str_replace('\0', '', $string)));
2069 /*! \brief Evaluate the current GOsa version from the build in revision string */
2070 function get_gosa_version()
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));
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);
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)
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);
2171 /*! \brief Clean the smarty compile dir */
2172 function clean_smarty_compile_dir($directory)
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   }
2213 function create_revision($revision_file, $revision)
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;
2232 function compare_revision($revision_file, $revision)
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;
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= "")
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   }
2301   
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>";
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)
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 ('');
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)
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);
2368 function saveFilter($a_filter, $values)
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);
2395 /*! \brief Escape all LDAP filter relevant characters */
2396 function normalizeLdap($input)
2398   return (addcslashes($input, '()|'));
2402 /*! \brief Return the gosa base directory */
2403 function get_base_dir()
2405   global $BASE_DIR;
2407   return $BASE_DIR;
2411 /*! \brief Test weither we are allowed to read the object */
2412 function obj_is_readable($dn, $object, $attribute)
2414   global $ui;
2416   return preg_match('/r/', $ui->get_permissions($dn, $object, $attribute));
2420 /*! \brief Test weither we are allowed to change the object */
2421 function obj_is_writable($dn, $object, $attribute)
2423   global $ui;
2425   return preg_match('/w/', $ui->get_permissions($dn, $object, $attribute));
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)
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);
2499 function get_base_from_hook($dn, $attrib)
2501   global $config;
2503   if ($config->get_cfg_value("core","baseIdHook") != ""){
2504     
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   }
2534 /*! \brief Check if schema version matches the requirements */
2535 function check_schema_version($class, $version)
2537   return preg_match("/\(v$version\)/", $class['DESC']);
2541 /*! \brief Check if LDAP schema matches the requirements */
2542 function check_schema($cfg,$rfc2307bis = FALSE)
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);
2675 function get_languages($languages_in_own_language = FALSE,$strip_region_tag = FALSE)
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");
2690   
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     }
2718     
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)." &nbsp;(".$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);
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)
2752     if(!isset($_POST[$name])){
2753         trigger_error("Requested POST value (".$name.") does not exist, you should add a check to prevent this message.");
2754         return(FALSE);
2755     }
2757     // Handle Posted Arrays
2758     $tmp = array();
2759     if(is_array($_POST[$name]) && !is_string($_POST[$name])){
2760         foreach($_POST[$name] as $key => $val){
2761             if(get_magic_quotes_gpc()){
2762                 $val = stripcslashes($val);
2763             }
2764             $tmp[$key] = $val;
2765         } 
2766         return($tmp);
2767     }else{
2769         if(get_magic_quotes_gpc()){
2770             $val = stripcslashes($_POST[$name]);
2771         }else{
2772             $val = $_POST[$name];
2773         }
2774     }
2775   return($val);
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)
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   }
2802 function set_post($value)
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     }
2811     
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'));
2821 /*! \brief Return class name in correct case */
2822 function get_correct_class_name($cls)
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);
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 = "")
2851     global $config;
2852     $newpass= "";
2854     // Not sure, why this is here, but maybe some encryption methods require it.
2855     mt_srand((double) microtime()*1000000);
2857     // Get a list of all available password encryption methods.
2858     $methods = new passwordMethod(session::get('config'),$dn);
2859     $available = $methods->get_available_methods();
2861     // Fetch the current object data, to be able to detect the current hashing method
2862     //  and to be able to rollback changes once has an error occured.
2863     $ldap = $config->get_ldap_link();
2864     $ldap->cat ($dn, array("shadowLastChange", "userPassword","sambaNTPassword","sambaLMPassword", "uid"));
2865     $attrs = $ldap->fetch ();
2866     $initialAttrs = $attrs;
2868     // If no hashing method is enforced, then detect what method we've to use.
2869     $hash = strtolower($hash);
2870     if(empty($hash)){
2872         // Do we need clear-text password for this object?
2873         if(isset($attrs['userPassword'][0]) && !preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0])){
2874             $hash = "clear";
2875             $test = new $available[$hash]($config,$dn);
2876             $test->set_hash($hash);
2877         }
2879         // If we've still no valid hashing method detected, then try to extract if from the userPassword attribute.
2880         elseif(isset($attrs['userPassword'][0]) && preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0], $matches)){
2881             $test = passwordMethod::get_method($attrs['userPassword'][0],$dn);
2882             $hash = $test->get_hash_name();
2883         }
2885         // No current password was found and no hash is enforced, so we've to use the config default here.
2886         $hash = $config->get_cfg_value('core','passwordDefaultHash');
2887         $test = new $available[$hash]($config,$dn);
2888         $test->set_hash($hash);
2889     }else{
2890         $test = new $available[$hash]($config,$dn);
2891         $test->set_hash($hash);
2892     }
2894     // We've now a valid password-method-handle and can create the new password hash or don't we?
2895     if(!$test instanceOf passwordMethod){
2896         $message = _("Cannot detect password hash!");
2897     }else{
2899         // Feed password backends with object information. 
2900         $test->dn = $dn;
2901         $test->attrs = $attrs;
2902         $newpass= $test->generate_hash($password);
2904         // Do we have to append samba attributes too?
2905         // - sambaNTPassword / sambaLMPassword
2906         $tmp = $config->get_cfg_value('core','sambaHashHook');
2907         $attrs= array();
2908         if (!$mode && !empty($tmp)){
2909             $attrs= generate_smb_nt_hash($password);
2910             if(!count($attrs) || !is_array($attrs)){
2911                 msg_dialog::display(_("Error"),_("Cannot generate SAMBA hash!"),ERROR_DIALOG);
2912                 return(FALSE);    
2913             }else{
2914                 $shadow = (isset($attrs["shadowLastChange"][0]))?(int)(date("U") / 86400):0;
2915                 if ($shadow != 0){
2916                     $attrs['shadowLastChange']= $shadow;
2917                 }
2918             }
2919         }
2921         // Write back the new password hash 
2922         $ldap->cd($dn);
2923         $attrs['userPassword']= $newpass;
2925         // Prepare a special attribute list, which will be used for event hook calls
2926         $attrsEvent = array();
2927         foreach($initialAttrs as $name => $value){
2928             if(!is_numeric($name))
2929                 $attrsEvent[$name] = escapeshellarg($value[0]);
2930         }
2931         $attrsEvent['dn'] = escapeshellarg($initialAttrs['dn']);
2932         foreach($attrs as $name => $value){
2933             $attrsEvent[$name] = escapeshellarg($value);
2934         }
2935         $attrsEvent['current_password'] = escapeshellarg($old_password);
2936         $attrsEvent['new_password'] = escapeshellarg($password);
2938         // Call the premodify hook now
2939         $passwordPlugin = new password($config,$dn);
2940         plugin::callHook($passwordPlugin, 'PREMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
2941         if($retCode === 0 && count($output)){
2942             $message = sprintf(_("Pre-event hook reported a problem: %s. Password change canceled!"),implode($output));
2943             return(FALSE);
2944         }
2946         // Perform ldap operations
2947         $ldap->modify($attrs);
2949         // Check if the object was locked before, if it was, lock it again!
2950         $deactivated = $test->is_locked($config,$dn);
2951         if($deactivated){
2952             $test->lock_account($config,$dn);
2953         }
2955         // Check if everything went fine and then call the post event hooks.
2956         // If an error occures, then try to rollback the complete actions done.
2957         $preRollback = FALSE;
2958         $ldapRollback = FALSE;
2959         $success = TRUE;
2960         if (!$ldap->success()) {
2961             new log("modify","users/passwordMethod",$dn,array(),"Password change - ldap modifications! - FAILED");
2962             $success =FALSE;
2963             $message = msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD);
2964             $preRollback  =TRUE;
2965         } else {
2967             // Now call the passwordMethod change mechanism.
2968             if(!$test->set_password($password)){
2969                 $ldapRollback = TRUE;
2970                 $preRollback  =TRUE;
2971                 $success = FALSE;
2972                 new log("modify","users/passwordMethod",$dn,array(),"Password change - set_password! - FAILED");
2973                 $message = _("Password change failed!");
2974             }else{
2975         
2976                 // Execute the password hook
2977                 plugin::callHook($passwordPlugin, 'POSTMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
2978                 if($retCode === 0){
2979                     if(count($output)){
2980                         new log("modify","users/passwordMethod",$dn,array(),"Password change - Post modify hook reported! - FAILED!");
2981                         $message = sprintf(_("Post-event hook reported a problem: %s. Password change canceled!"),implode($output));
2982                         $ldapRollback = TRUE;
2983                         $preRollback = TRUE;
2984                         $success = FALSE;
2985                     }else{
2986                         #new log("modify","users/passwordMethod",$dn,array(),"Password change - successfull!");
2987                     }
2988                 }else{
2989                     $ldapRollback = TRUE;
2990                     $preRollback = TRUE;
2991                     $success = FALSE;
2992                     new log("modify","users/passwordMethod",$dn,array(),"Password change - postmodify hook execution! - FAILED");
2993                     new log("modify","users/passwordMethod",$dn,array(),$error);
2995                     // Call password method again and send in old password to 
2996                     //  keep the database consistency
2997                     $test->set_password($old_password);
2998                 }
2999             }
3000         }
3002         // Setting the password in the ldap database or further operation failed, we should now execute 
3003         //  the plugins pre-event hook, using switched passwords, new/old password.
3004         // This ensures that passwords which were set outside of GOsa, will be reset to its 
3005         //  starting value.
3006         if($preRollback){
3007             new log("modify","users/passwordMethod",$dn,array(),"Rolling back premodify hook!");
3008             $oldpass= $test->generate_hash($old_password);
3009             $attrsEvent['current_password'] = escapeshellarg($password);
3010             $attrsEvent['new_password'] = escapeshellarg($old_password);
3011             foreach(array("userPassword","sambaNTPassword","sambaLMPassword") as $attr){
3012                 if(isset($initialAttrs[$attr][0])) $attrsEvent[$attr] = $initialAttrs[$attr][0];
3013             }
3014             
3015             plugin::callHook($passwordPlugin, 'PREMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
3016             if($retCode === 0 && count($output)){
3017                 $message = sprintf(_("Pre-event hook reported a problem: %s. Password change canceled!"),implode($output));
3018                 new log("modify","users/passwordMethod",$dn,array(),"Rolling back premodify hook! - FAILED!");
3019             }
3020         }
3021         
3022         // We've written the password to the ldap database, but executing the postmodify hook failed.
3023         // Now, we've to rollback all password related ldap operations.
3024         if($ldapRollback){
3025             new log("modify","users/passwordMethod",$dn,array(),"Rolling back ldap modifications!");
3026             $attrs = array();
3027             foreach(array("userPassword","sambaNTPassword","sambaLMPassword") as $attr){
3028                 if(isset($initialAttrs[$attr][0])) $attrs[$attr] = $initialAttrs[$attr][0];
3029             }
3030             $ldap->cd($dn);
3031             $ldap->modify($attrs);
3032             if(!$ldap->success()){
3033                 $message = msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD);
3034                 new log("modify","users/passwordMethod",$dn,array(),"Rolling back ldap modifications! - FAILED");
3035             }
3036         }
3038         // Log action.
3039         if($success){
3040             stats::log('global', 'global', array('users'),  $action = 'change_password', $amount = 1, 0, $test->get_hash());
3041             new log("modify","users/passwordMethod",$dn,array(),"Password change - successfull!");
3042         }else{
3043             new log("modify","users/passwordMethod",$dn,array(),"Password change - FAILED!");
3044         }
3046         return($success);
3047     }
3051 /*! \brief Generate samba hashes
3052  *
3053  * Given a certain password this constructs an array like
3054  * array['sambaLMPassword'] etc.
3055  *
3056  * \param string 'password'
3057  * \return array contains several keys for lmPassword, ntPassword, pwdLastSet, etc. depending
3058  * on the samba version
3059  */
3060 function generate_smb_nt_hash($password)
3062   global $config;
3064   // First try to retrieve values via RPC 
3065   if ($config->get_cfg_value("core","gosaRpcServer") != ""){
3067     $rpc = $config->getRpcHandle();
3068     $hash = $rpc->mksmbhash($password);
3069     if(!$rpc->success()){
3070         msg_dialog::display(_("Error"),msgPool::rpcError($rpc->get_error()),ERROR_DIALOG);
3071         return(array());
3072     }
3074   }elseif ($config->get_cfg_value("core","gosaSupportURI") != ""){
3076     // Try using gosa-si
3077         $res= gosaSupportDaemon::send("gosa_gen_smb_hash", "GOSA", array("password" => $password), TRUE);
3078     if (isset($res['XML']['HASH'])){
3079         $hash= $res['XML']['HASH'];
3080     } else {
3081       $hash= "";
3082     }
3084     if ($hash == "") {
3085       msg_dialog::display(_("Configuration error"), _("Cannot generate SAMBA hash!"), ERROR_DIALOG);
3086       return ("");
3087     }
3088   } else {
3089           $tmp = $config->get_cfg_value("core",'sambaHashHook');
3090       $tmp = preg_replace("/%userPassword/", escapeshellarg($password), $tmp);
3091       $tmp = preg_replace("/%password/", escapeshellarg($password), $tmp);
3092           @DEBUG (DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $tmp, "Execute");
3094           exec($tmp, $ar);
3095           flush();
3096           reset($ar);
3097           $hash= current($ar);
3099     if ($hash == "") {
3100       msg_dialog::display(_("Configuration error"), sprintf(_("Generating SAMBA hash by running %s failed: check %s!"), bold($config->get_cfg_value("core",'sambaHashHook'), bold("sambaHashHook"))), ERROR_DIALOG);
3101       return(array());
3102     }
3103   }
3105   list($lm,$nt)= explode(":", trim($hash));
3107   $attrs['sambaLMPassword']= $lm;
3108   $attrs['sambaNTPassword']= $nt;
3109   $attrs['sambaPwdLastSet']= date('U');
3110   $attrs['sambaBadPasswordCount']= "0";
3111   $attrs['sambaBadPasswordTime']= "0";
3112   return($attrs);
3116 /*! \brief Get the Change Sequence Number of a certain DN
3117  *
3118  * To verify if a given object has been changed outside of Gosa
3119  * in the meanwhile, this function can be used to get the entryCSN
3120  * from the LDAP directory. It uses the attribute as configured
3121  * in modificationDetectionAttribute
3122  *
3123  * \param string 'dn'
3124  * \return either the result or "" in any other case
3125  */
3126 function getEntryCSN($dn)
3128   global $config;
3129   if(empty($dn) || !is_object($config)){
3130     return("");
3131   }
3133   /* Get attribute that we should use as serial number */
3134   $attr= $config->get_cfg_value("core","modificationDetectionAttribute");
3135   if($attr != ""){
3136     $ldap = $config->get_ldap_link();
3137     $ldap->cat($dn,array($attr));
3138     $csn = $ldap->fetch();
3139     if(isset($csn[$attr][0])){
3140       return($csn[$attr][0]);
3141     }
3142   }
3143   return("");
3147 /*! \brief Add (a) given objectClass(es) to an attrs entry
3148  * 
3149  * The function adds the specified objectClass(es) to the given
3150  * attrs entry.
3151  *
3152  * \param mixed 'classes' Either a single objectClass or several objectClasses
3153  * as an array
3154  * \param array 'attrs' The attrs array to be modified.
3155  *
3156  * */
3157 function add_objectClass($classes, &$attrs)
3159   if (is_array($classes)){
3160     $list= $classes;
3161   } else {
3162     $list= array($classes);
3163   }
3165   foreach ($list as $class){
3166     $attrs['objectClass'][]= $class;
3167   }
3171 /*! \brief Removes a given objectClass from the attrs entry
3172  *
3173  * Similar to add_objectClass, except that it removes the given
3174  * objectClasses. See it for the params.
3175  * */
3176 function remove_objectClass($classes, &$attrs)
3178   if (isset($attrs['objectClass'])){
3179     /* Array? */
3180     if (is_array($classes)){
3181       $list= $classes;
3182     } else {
3183       $list= array($classes);
3184     }
3186     $tmp= array();
3187     foreach ($attrs['objectClass'] as $oc) {
3188       foreach ($list as $class){
3189         if (strtolower($oc) != strtolower($class)){
3190           $tmp[]= $oc;
3191         }
3192       }
3193     }
3194     $attrs['objectClass']= $tmp;
3195   }
3199 /*! \brief  Initialize a file download with given content, name and data type. 
3200  *  \param  string data The content to send.
3201  *  \param  string name The name of the file.
3202  *  \param  string type The content identifier, default value is "application/octet-stream";
3203  */
3204 function send_binary_content($data,$name,$type = "application/octet-stream")
3206   header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
3207   header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
3208   header("Cache-Control: no-cache");
3209   header("Pragma: no-cache");
3210   header("Cache-Control: post-check=0, pre-check=0");
3211   header("Content-type: ".$type."");
3213   $HTTP_USER_AGENT = $_SERVER['HTTP_USER_AGENT'];
3215   /* Strip name if it is a complete path */
3216   if (preg_match ("/\//", $name)) {
3217         $name= basename($name);
3218   }
3219   
3220   /* force download dialog */
3221   if (preg_match('/MSIE 5.5/', $HTTP_USER_AGENT) || preg_match('/MSIE 6.0/', $HTTP_USER_AGENT)) {
3222     header('Content-Disposition: filename="'.$name.'"');
3223   } else {
3224     header('Content-Disposition: attachment; filename="'.$name.'"');
3225   }
3227   echo $data;
3228   exit();
3232 function reverse_html_entities($str,$type = ENT_QUOTES , $charset = "UTF-8")
3234   if(is_string($str)){
3235     return(htmlentities($str,$type,$charset));
3236   }elseif(is_array($str)){
3237     foreach($str as $name => $value){
3238       $str[$name] = reverse_html_entities($value,$type,$charset);
3239     }
3240   }
3241   return($str);
3245 /*! \brief Encode special string characters so we can use the string in \
3246            HTML output, without breaking quotes.
3247     \param string The String we want to encode.
3248     \return string The encoded String
3249  */
3250 function xmlentities($str)
3251
3252   if(is_string($str)){
3254     static $asc2uni= array();
3255     if (!count($asc2uni)){
3256       for($i=128;$i<256;$i++){
3257     #    $asc2uni[chr($i)] = "&#x".dechex($i).";";
3258       }
3259     }
3261     $str = str_replace("&", "&amp;", $str);
3262     $str = str_replace("<", "&lt;", $str);
3263     $str = str_replace(">", "&gt;", $str);
3264     $str = str_replace("'", "&apos;", $str);
3265     $str = str_replace("\"", "&quot;", $str);
3266     $str = str_replace("\r", "", $str);
3267     $str = strtr($str,$asc2uni);
3268     return $str;
3269   }elseif(is_array($str)){
3270     foreach($str as $name => $value){
3271       $str[$name] = xmlentities($value);
3272     }
3273   }
3274   return($str);
3278 /*! \brief  Updates all accessTo attributes from a given value to a new one.
3279             For example if a host is renamed.
3280     \param  String  $from The source accessTo name.
3281     \param  String  $to   The destination accessTo name.
3282 */
3283 function update_accessTo($from,$to)
3285   global $config;
3286   $ldap = $config->get_ldap_link();
3287   $ldap->cd($config->current['BASE']);
3288   $ldap->search("(&(objectClass=trustAccount)(accessTo=".$from."))",array("objectClass","accessTo"));
3289   while($attrs = $ldap->fetch()){
3290     $new_attrs = array("accessTo" => array());
3291     $dn = $attrs['dn'];
3292     for($i = 0 ; $i < $attrs['accessTo']['count']; $i++){
3293       if($attrs['accessTo'][$i] == $from){
3294         if(!empty($to)){
3295           $new_attrs['accessTo'][] =  $to;
3296         }
3297       }else{
3298         $new_attrs['accessTo'][] =  $attrs['accessTo'][$i]; 
3299       }
3300     }
3301     $ldap->cd($dn);
3302     $ldap->modify($new_attrs);
3303     if (!$ldap->success()){
3304       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD, "update_accessTo($from,$to)"));
3305     }
3306     new log("modify","update_accessTo($from,$to)",$dn,array_keys($new_attrs),$ldap->get_error());
3307   }
3311 /*! \brief Returns a random char */
3312 function get_random_char () {
3313      $randno = rand (0, 63);
3314      if ($randno < 12) {
3315          return (chr ($randno + 46)); // Digits, '/' and '.'
3316      } else if ($randno < 38) {
3317          return (chr ($randno + 53)); // Uppercase
3318      } else {
3319          return (chr ($randno + 59)); // Lowercase
3320      }
3324 function cred_encrypt($input, $password) {
3326   $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
3327   $iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
3329   return bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $password, $input, MCRYPT_MODE_ECB, $iv));
3334 function cred_decrypt($input,$password) {
3335   $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
3336   $iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
3338   return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, pack("H*", $input), MCRYPT_MODE_ECB, $iv);
3342 function get_object_info()
3344   return(session::get('objectinfo'));
3348 function set_object_info($str = "")
3350   session::set('objectinfo',$str);
3354 function isIpInNet($ip, $net, $mask) {
3355    // Move to long ints
3356    $ip= ip2long($ip);
3357    $net= ip2long($net);
3358    $mask= ip2long($mask);
3360    // Mask given IP with mask. If it returns "net", we're in...
3361    $res= $ip & $mask;
3363    return ($res == $net);
3367 function get_next_id($attrib, $dn)
3369   global $config;
3371   switch ($config->get_cfg_value("core","idAllocationMethod")){
3372     case "pool":
3373       return get_next_id_pool($attrib);
3374     case "traditional":
3375       return get_next_id_traditional($attrib, $dn);
3376   }
3378   msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("unknown idAllocation method!"), ERROR_DIALOG);
3379   return null;
3383 function get_next_id_pool($attrib) {
3384   global $config;
3386   /* Fill informational values */
3387   $min= $config->get_cfg_value("core","${attrib}PoolMin");
3388   $max= $config->get_cfg_value("core","${attrib}PoolMax");
3390   /* Sanity check */
3391   if ($min >= $max) {
3392     msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." ".sprintf(_("%sPoolMin >= %sPoolMax!"), bold($attrib), bold($attrib)), ERROR_DIALOG);
3393     return null;
3394   }
3396   /* ID to skip */
3397   $ldap= $config->get_ldap_link();
3398   $id= null;
3400   /* Try to allocate the ID several times before failing */
3401   $tries= 3;
3402   while ($tries--) {
3404     /* Look for ID map entry */
3405     $ldap->cd ($config->current['BASE']);
3406     $ldap->search ("(&(objectClass=sambaUnixIdPool)($attrib=*))", array("$attrib"));
3408     /* If it does not exist, create one with these defaults */
3409     if ($ldap->count() == 0) {
3410       /* Fill informational values */
3411       $minUserId= $config->get_cfg_value("core","uidNumberPoolMin");
3412       $minGroupId= $config->get_cfg_value("core","gidNumberPoolMin");
3414       /* Add as default */
3415       $attrs= array("objectClass" => array("organizationalUnit", "sambaUnixIdPool"));
3416       $attrs["ou"]= "idmap";
3417       $attrs["uidNumber"]= $minUserId;
3418       $attrs["gidNumber"]= $minGroupId;
3419       $ldap->cd("ou=idmap,".$config->current['BASE']);
3420       $ldap->add($attrs);
3421       if ($ldap->error != "Success") {
3422         msg_dialog::display(_("Error"), _("Cannot create sambaUnixIdPool entry!"), ERROR_DIALOG);
3423         return null;
3424       }
3425       $tries++;
3426       continue;
3427     }
3428     /* Bail out if it's not unique */
3429     if ($ldap->count() != 1) {
3430       msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("sambaUnixIdPool is not unique!"), ERROR_DIALOG);
3431       return null;
3432     }
3434     /* Store old attrib and generate new */
3435     $attrs= $ldap->fetch();
3436     $dn= $ldap->getDN();
3437     $oldAttr= $attrs[$attrib][0];
3438     $newAttr= $oldAttr + 1;
3440     /* Sanity check */
3441     if ($newAttr >= $max) {
3442       msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("no ID available!"), ERROR_DIALOG);
3443       return null;
3444     }
3445     if ($newAttr < $min) {
3446       msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("no ID available!"), ERROR_DIALOG);
3447       return null;
3448     }
3450     #FIXME: PHP is not able to do a modification of "del: .../add: ...", so this
3451     #       is completely unsafe in the moment.
3452     #/* Remove old attr, add new attr */
3453     #$attrs= array($attrib => $oldAttr);
3454     #$ldap->rm($attrs, $dn);
3455     #if ($ldap->error != "Success") {
3456     #  continue;
3457     #}
3458     $ldap->cd($dn);
3459     $ldap->modify(array($attrib => $newAttr));
3460     if ($ldap->error != "Success") {
3461       msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." ".$ldap->get_error(), ERROR_DIALOG);
3462       return null;
3463     } else {
3464       return $oldAttr;
3465     }
3466   }
3468   /* Bail out if we had problems getting the next id */
3469   if (!$tries) {
3470     msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("maximum number of tries exceeded!"), ERROR_DIALOG);
3471   }
3473   return $id;
3477 function get_next_id_traditional($attrib, $dn)
3479   global $config;
3481   $ids= array();
3482   $ldap= $config->get_ldap_link();
3484   $ldap->cd ($config->current['BASE']);
3485   if (preg_match('/gidNumber/i', $attrib)){
3486     $oc= "posixGroup";
3487   } else {
3488     $oc= "posixAccount";
3489   }
3490   $ldap->search ("(&(objectClass=$oc)($attrib=*))", array("$attrib"));
3492   /* Get list of ids */
3493   while ($attrs= $ldap->fetch()){
3494     $ids[]= (int)$attrs["$attrib"][0];
3495   }
3497   /* Add the nobody id */
3498   $ids[]= 65534;
3500   /* get the ranges */
3501   $tmp = array('0'=> 1000);
3502   if (preg_match('/posixAccount/', $oc) && $config->get_cfg_value("core","uidNumberBase") != ""){
3503     $tmp= explode('-',$config->get_cfg_value("core","uidNumberBase"));
3504   } elseif($config->get_cfg_value("core","gidNumberBase") != ""){
3505     $tmp= explode('-',$config->get_cfg_value("core","gidNumberBase"));
3506   }
3508   /* Set hwm to max if not set - for backward compatibility */
3509   $lwm= $tmp[0];
3510   if (isset($tmp[1])){
3511     $hwm= $tmp[1];
3512   } else {
3513     $hwm= pow(2,32);
3514   }
3515   /* Find out next free id near to UID_BASE */
3516   if ($config->get_cfg_value("core","baseIdHook") == ""){
3517     $base= $lwm;
3518   } else {
3519     /* Call base hook */
3520     $base= get_base_from_hook($dn, $attrib);
3521   }
3522   for ($id= $base; $id++; $id < pow(2,32)){
3523     if (!in_array_strict($id, $ids)){
3524       return ($id);
3525     }
3526   }
3528   /* Should not happen */
3529   if ($id == $hwm){
3530     msg_dialog::display(_("Error"), _("Cannot allocate free ID!"), ERROR_DIALOG);
3531     exit;
3532   }
3536 /* Mark the occurance of a string with a span */
3537 function mark($needle, $haystack, $ignorecase= true)
3539   $result= "";
3541   while (preg_match('/^(.*)('.preg_quote($needle).')(.*)$/i', $haystack, $matches)) {
3542     $result.= $matches[1]."<span class='mark'>".$matches[2]."</span>";
3543     $haystack= $matches[3];
3544   }
3546   return $result.$haystack;
3550 /* Return an image description using the path */
3551 function image($path, $action= "", $title= "", $align= "middle")
3553   global $config;
3554   global $BASE_DIR;
3555   $label= null;
3557   // Bail out, if there's no style file
3558   if(!class_exists('session')){
3559     return "";    
3560   }
3561   if(!session::global_is_set("img-styles")){
3563     // Get theme
3564     if (isset ($config)){
3565       $theme= $config->get_cfg_value("core","theme");
3566     } else {
3568       // Fall back to default theme
3569       $theme= "default";
3570     }
3572     if (!file_exists("$BASE_DIR/ihtml/themes/$theme/img.styles")){
3573       die ("No img.style for this theme found!");
3574     }
3576     session::global_set('img-styles', unserialize(file_get_contents("$BASE_DIR/ihtml/themes/$theme/img.styles")));
3577   }
3578   $styles= session::global_get('img-styles');
3580   /* Extract labels from path */
3581   if (preg_match("/\.png\[(.*)\]$/", $path, $matches)) {
3582     $label= $matches[1];
3583   }
3585   $lbl= "";
3586   if ($label) {
3587     if (isset($styles["images/label-".$label.".png"])) {
3588       $lbl= "<div style='".$styles["images/label-".$label.".png"]."'></div>";
3589     } else {
3590       die("Invalid label specified: $label\n");
3591     }
3593     $path= preg_replace("/\[.*\]$/", "", $path);
3594   }
3596   // Non middle layout?
3597   if ($align == "middle") {
3598     $align= "";
3599   } else {
3600     $align= ";vertical-align:$align";
3601   }
3603   // Clickable image or not?
3604   if ($title != "") {
3605     $title= "title='$title'";
3606   }
3607   if ($action == "") {
3608     return "<div class='img' $title style='".$styles[$path]."$align'>$lbl</div>";
3609   } else {
3610     return "<input type='submit' class='img' id='$action' value='' name='$action' $title style='".$styles[$path]."$align'>";
3611   }
3614 /*! \brief    Encodes a complex string to be useable in HTML posts.
3615  */
3616 function postEncode($str)
3618   return(preg_replace("/=/","_", base64_encode($str)));
3621 /*! \brief    Decodes a string encoded by postEncode
3622  */
3623 function postDecode($str)
3625   return(base64_decode(preg_replace("/_/","=", $str)));
3629 /*! \brief    Generate styled output
3630  */
3631 function bold($str)
3633   return "<span class='highlight'>$str</span>";
3638 /*! \brief  Detect the special character handling for the currently used ldap database. 
3639  *          For example some convert , to \2C or " to \22.
3640  *         
3641  *  @param      Config  The GOsa configuration object.
3642  *  @return     Array   An array containing a character mapping the use.
3643  */
3644 function detectLdapSpecialCharHandling()
3646     // The list of chars to test for
3647     global $config;
3648     if(!$config) return(NULL);
3650     // In the DN we've to use escaped characters, but the object name (o)
3651     //  has the be un-escaped.
3652     $name = 'GOsaLdapEncoding_,_"_(_)_+_/';
3653     $dnName = 'GOsaLdapEncoding_\,_\"_(_)_\+_/';
3654    
3655     // Prapare name to be useable in filters
3656     $fixed= normalizeLdap(str_replace('\\\\', '\\\\\\', $name));
3657     $filterName = str_replace('\\,', '\\\\,', $fixed);
3658  
3659     // Create the target dn
3660     $oDN = "o={$dnName},".$config->current['BASE'];
3662     // Get ldap connection and check if we've already created the character 
3663     //  detection object. 
3664     $ldapCID = ldap_connect($config->current['SERVER']);
3665     ldap_set_option($ldapCID, LDAP_OPT_PROTOCOL_VERSION, 3);
3666     ldap_bind($ldapCID, $config->current['ADMINDN'],$config->current['ADMINPASSWORD']);
3667     $res = ldap_list($ldapCID, $config->current['BASE'], 
3668             "(&(o=".$filterName.")(objectClass=organization))",
3669             array('dn'));
3671     // If we haven't created the character-detection object, then create it now.
3672     $cnt = ldap_count_entries($ldapCID, $res);
3673     if(!$cnt){
3674         $obj = array();
3675         $obj['objectClass'] = array('top','organization');
3676         $obj['o'] = $name;
3677         $obj['description'] = 'GOsa character encoding test-object.';
3678         if(!@ldap_add($ldapCID, $oDN, $obj)){
3679             trigger_error("GOsa couldn't detect the special character handling used by your ldap!");
3680             return(NULL);
3681         }
3682     }
3683     
3684     // Read the character-handling detection entry from the ldap.
3685     $res = ldap_list($ldapCID, $config->current['BASE'],
3686             "(&(o=".$filterName.")(objectClass=organization))",
3687             array('dn','o'));
3688     $cnt = ldap_count_entries($ldapCID, $res);
3689     if($cnt != 1 || !$res){
3690         trigger_error("GOsa couldn't detect the special character handling used by your ldap!");
3691         return(NULL);
3692     }else{
3694         // Get the character handling entry from the ldap and check how the 
3695         //  values were written. Compare them with what
3696         //  we've initially intended to write and create a mapping out 
3697         //  of the results.
3698         $re = ldap_first_entry($ldapCID, $res);
3699         $attrs = ldap_get_attributes($ldapCID, $re);
3700    
3701         // Extract the interessting characters out of the dn and the 
3702         //  initially used $name for the entry. 
3703         $mapDNstr = preg_replace("/^o=GOsaLdapEncoding_([^,]*),.*$/","\\1", trim(ldap_get_dn($ldapCID, $re)));
3704         $mapDN = preg_split("/_/", $mapDNstr,0, PREG_SPLIT_NO_EMPTY);
3706         $mapNameStr = preg_replace("/^GOsaLdapEncoding_/","",$dnName);
3707         $mapName = preg_split("/_/", $mapNameStr,0, PREG_SPLIT_NO_EMPTY);
3709         // Create a mapping out of the results.
3710         $map = array();
3711         foreach($mapName as $key => $entry){
3712             $map[$entry] = $mapDN[$key];
3713         }
3714         return($map);
3715     }
3716     return(NULL);
3720 /*! \brief  Replaces placeholder in a given string.
3721  *          For example:
3722  *            '%uid@gonicus.de'         Replaces '%uid' with 'uid'.
3723  *            '{%uid[0]@gonicus.de}'    Replaces '%uid[0]' with the first char of 'uid'.
3724  *            '%uid[2-4]@gonicus.de'    Replaces '%uid[2-4]' with three chars from 'uid' starting from the second.
3725  *      
3726  *          The surrounding {} in example 2 are optional.
3727  *
3728  *  @param  String  The string to perform the action on.
3729  *  @param  Array   An array of replacements.
3730  *  @return     The resulting string.
3731  */
3732 function fillReplacements($str, $attrs, $shellArg = FALSE, $default = "")
3734     // Search for '{%...[n-m]}
3735     // Get all matching parts of the given string and sort them by
3736     //  length, to avoid replacing strings like '%uidNumber' with 'uid'
3737     //  instead of 'uidNumber'; The longest tring at first.
3738     preg_match_all('/(\{?%([a-z0-9_]+)(\[(([0-9_]+)(\-([0-9_]+))?)\])?\}?)/i', $str ,$matches, PREG_SET_ORDER);
3739     $hits = array();
3740     foreach($matches as $match){
3741         $hits[strlen($match[2]).$match[0]] = $match;
3742     }
3743     krsort($hits);
3745     // Add lower case placeholders to avoid errors
3746     foreach($attrs as $key => $attr) $attrs[strtolower($key)] = $attr;
3748     // Replace the placeholder in the given string now.
3749     foreach($hits as $match){
3751         // Avoid errors about undefined index.
3752         $name = strtolower($match[2]);
3753         if(!isset($attrs[$name])) $attrs[$name] = $default;
3755         // Calculate the replacement
3756         $start = (isset($match[5])) ? $match[5] : 0;
3757         $end = strlen($attrs[$name]);
3758         if(isset($match[5]) && !isset($match[7])){
3759             $end = 1;
3760         }elseif(isset($match[5]) && isset($match[7])){
3761             $end = ($match[7]-$start+1);
3762         }
3763         $value  = substr($attrs[$name], $start, $end);
3765         // Use values which are valid for shell execution?
3766         if($shellArg) $value = escapeshellarg($value);
3768         // Replace the placeholder within the string.
3769         $str = preg_replace("/".preg_quote($match[0],'/')."/", $value, $str);
3770     }
3771     return($str);
3775 /*! \brief Generate a list of uid proposals based on a rule
3776  *
3777  *  Unroll given rule string by filling in attributes and replacing
3778  *  all keywords.
3779  *
3780  * \param string 'rule' The rule string from gosa.conf.
3781  * \param array 'attributes' A dictionary of attribute/value mappings
3782  * \return array List of valid not used uids
3783  */
3784 function gen_uids($rule, $attributes)
3786     global $config;
3787     $ldap = $config->get_ldap_link();
3788     $ldap->cd($config->current['BASE']);
3790     @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $rule, "Processing");
3791     
3792     // Strip out non ascii chars
3793     foreach($attributes as $name => $value){
3794         if ( $config->get_cfg_value("core", "forceTranslit") == "true" ) {
3795              $value = cyrillic2ascii($value);
3796         } else {
3797              $value = iconv('UTF-8', 'US-ASCII//TRANSLIT', $value);
3798         }
3799         $value = preg_replace('/[^(\x20-\x7F)]*/','',$value);
3800         $attributes[$name] = strtolower($value);
3801     }
3802     
3803     @DEBUG (DEBUG_TRACE, __LINE__, __FUNCTION__, __FILE__, $attributes, "Prepare");
3805     // Search for '{%...[n-m]}
3806     // Get all matching parts of the given string and sort them by
3807     //  length, to avoid replacing strings like '%uidNumber' with 'uid'
3808     //  instead of 'uidNumber'; The longest tring at first.
3809     preg_match_all('/(\{?%([a-z0-9]+)(\[(([0-9]+)(\-([0-9]+))?)\])?\}?)/i', $rule ,$matches, PREG_SET_ORDER);
3810     $replacements = array(); 
3811     foreach($matches as $match){
3812         
3813         // No start position given, then add the complete value
3814         if(!isset($match[5])){
3815             $replacements[$match[0]][] = $attributes[$match[2]];
3816     
3817         // Start given but no end, so just add a single character
3818         }elseif(!isset($match[7])){
3819             if(isset($attributes[$match[2]][$match[5]])){
3820                 $tmp = " ".$attributes[$match[2]];
3821                 $replacements[$match[0]][] = trim($tmp[$match[5]]);
3822             }
3824         // Add all values in range
3825         }else{
3826             $str = "";
3827             for($i=$match[5]; $i<= $match[7]; $i++){
3828                 if(isset($attributes[$match[2]][$i])){
3829                     $tmp = " ".$attributes[$match[2]];
3830                     $str .= $tmp[$i];
3831                     $replacements[$match[0]][] = trim($str);
3832                 }
3833             }
3834         }
3835     }
3837     // Create proposal array
3838     $rules = array($rule);
3839     foreach($replacements as $tag => $values){
3840         $rules = gen_uid_proposals($rules, $tag, $values);
3841     }
3842     
3844     // Search for id tags {id:3} / {id#3}
3845     preg_match_all('/\{id(#|:)([0-9])+\}/i', $rule, $matches, PREG_SET_ORDER);
3846     $idReplacements = array();
3847     foreach($matches as $match){
3848         if(count($match) != 3) continue;
3850         // Generate random number 
3851         if($match[1] == '#'){
3852             foreach($rules as $id => $ruleStr){
3853                 $genID = rand(pow(10,$match[2] -1),pow(10, ($match[2])) - 1);
3854                 $rules[$id] = preg_replace("/".preg_quote($match[0],'/')."/", $genID,$ruleStr);
3855             }
3856         }
3857     
3858         // Search for next free id 
3859         if($match[1] == ':'){
3861             // Walk through rules and replace all occurences of {id:..}
3862             foreach($rules as $id => $ruleStr){
3863                 $genID = 0;
3864                 $start = TRUE;
3865                 while($start || $ldap->count()){
3866                     $start = FALSE;
3867                     $number= sprintf("%0".$match[2]."d", $genID);
3868                     $testRule = preg_replace("/".preg_quote($match[0],'/')."/",$number,$ruleStr); 
3869                     $ldap->search('uid='.normalizeLdap($testRule));
3870                     $genID ++;
3871                 }
3872                 $rules[$id] = preg_replace("/".preg_quote($match[0],'/')."/",$number,$ruleStr);
3873             }
3874         }
3875     }
3877     // Create result set by checking which uid is already used and which is free.
3878     $ret = array();
3879     foreach($rules as $rule){
3880         $ldap->search('uid='.normalizeLdap($rule));
3881         if(!$ldap->count()){
3882             $ret[] =  $rule;
3883         }
3884     }
3885    
3886     return($ret);
3890 function gen_uid_proposals(&$rules, $tag, $values)
3892     $newRules = array();
3893     foreach($rules as $rule){
3894         foreach($values as $value){
3895             $newRules[] = preg_replace("/".preg_quote($tag,'/')."/", $value, $rule); 
3896         }
3897     }
3898     return($newRules);
3902 function gen_uuid() 
3904     return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
3905         // 32 bits for "time_low"
3906         mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
3908         // 16 bits for "time_mid"
3909         mt_rand( 0, 0xffff ),
3911         // 16 bits for "time_hi_and_version",
3912         // four most significant bits holds version number 4
3913         mt_rand( 0, 0x0fff ) | 0x4000,
3915         // 16 bits, 8 bits for "clk_seq_hi_res",
3916         // 8 bits for "clk_seq_low",
3917         // two most significant bits holds zero and one for variant DCE1.1
3918         mt_rand( 0, 0x3fff ) | 0x8000,
3920         // 48 bits for "node"
3921         mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
3922     );
3925 function gosa_file_name($filename)
3927     $tempfile = tempnam(sys_get_temp_dir(), 'GOsa'); 
3928     if(move_uploaded_file($filename, $tempfile)){ 
3929        return( $tempfile);
3930     }
3933 function gosa_file($filename)
3935     $tempfile = tempnam(sys_get_temp_dir(), 'GOsa'); 
3936     if(move_uploaded_file($filename, $tempfile)){ 
3937        return file( $tempfile );
3938     }
3941 function gosa_fopen($filename, $mode)
3943     $tempfile = tempnam(sys_get_temp_dir(), 'GOsa'); 
3944     if(move_uploaded_file($filename, $tempfile)){ 
3945        return fopen( $tempfile, $mode );
3946     }
3950 /*\brief    Our own in_array method which defaults to a strict mode.
3951  */
3952 function in_array_strict($needle, $haystack, $strict = TRUE)
3954     return(in_array($needle, $haystack, $strict));
3957 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
3958 ?>