Code

Updated debugLevel handling
[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 /* Configuration file location */
27 if(!isset($_SERVER['CONFIG_DIR'])){
28   define ("CONFIG_DIR", "/etc/gosa");
29 }else{
30   define ("CONFIG_DIR",$_SERVER['CONFIG_DIR']);
31 }
33 /* Allow setting the config file in the apache configuration
34     e.g.  SetEnv CONFIG_FILE gosa.conf.2.6
35  */
36 if(!isset($_SERVER['CONFIG_FILE'])){
37   define ("CONFIG_FILE", "gosa.conf");
38 }else{
39   define ("CONFIG_FILE",$_SERVER['CONFIG_FILE']);
40 }
42 /* Define common locatitions */
43 define ("CONFIG_TEMPLATE_DIR", "../contrib");
44 define ("TEMP_DIR","/var/cache/gosa/tmp");
46 /* Define get_list flags */
47 define("GL_NONE",         0);
48 define("GL_SUBSEARCH",    1);
49 define("GL_SIZELIMIT",    2);
50 define("GL_CONVERT",      4);
51 define("GL_NO_ACL_CHECK", 8);
53 /* Heimdal stuff */
54 define('UNIVERSAL',0x00);
55 define('INTEGER',0x02);
56 define('OCTET_STRING',0x04);
57 define('OBJECT_IDENTIFIER ',0x06);
58 define('SEQUENCE',0x10);
59 define('SEQUENCE_OF',0x10);
60 define('SET',0x11);
61 define('SET_OF',0x11);
62 define('DEBUG',false);
63 define('HDB_KU_MKEY',0x484442);
64 define('TWO_BIT_SHIFTS',0x7efc);
65 define('DES_CBC_CRC',1);
66 define('DES_CBC_MD4',2);
67 define('DES_CBC_MD5',3);
68 define('DES3_CBC_MD5',5);
69 define('DES3_CBC_SHA1',16);
71 /* Define globals for revision comparing */
72 $svn_path = '$HeadURL$';
73 $svn_revision = '$Revision$';
75 /* Include required files */
76 require_once("class_location.inc");
77 require_once ("functions_debug.inc");
78 require_once ("accept-to-gettext.inc");
80 /* Define constants for debugging */
81 define ("DEBUG_TRACE",   1); /*! Debug level for tracing of common actions (save, check, etc.) */
82 define ("DEBUG_LDAP",    2); /*! Debug level for LDAP queries */
83 define ("DEBUG_MYSQL",   4); /*! Debug level for mysql operations */
84 define ("DEBUG_SHELL",   8); /*! Debug level for shell commands */
85 define ("DEBUG_POST",   16); /*! Debug level for POST content */
86 define ("DEBUG_SESSION",32); /*! Debug level for SESSION content */
87 define ("DEBUG_CONFIG", 64); /*! Debug level for CONFIG information */
88 define ("DEBUG_ACL",    128); /*! Debug level for ACL infos */
89 define ("DEBUG_SI",     256); /*! Debug level for communication with gosa-si */
90 define ("DEBUG_MAIL",   512); /*! Debug level for all about mail (mailAccounts, imap, sieve etc.) */
91 define ("DEBUG_FAI",   1024); // FAI (incomplete)
92 define ("DEBUG_RPC",   2048); /*! Debug level for communication with remote procedures */
94 /* Rewrite german 'umlauts' and spanish 'accents'
95    to get better results */
96 $REWRITE= array( "ä" => "ae",
97     "ö" => "oe",
98     "ü" => "ue",
99     "Ä" => "Ae",
100     "Ö" => "Oe",
101     "Ü" => "Ue",
102     "ß" => "ss",
103     "á" => "a",
104     "é" => "e",
105     "í" => "i",
106     "ó" => "o",
107     "ú" => "u",
108     "Á" => "A",
109     "É" => "E",
110     "Í" => "I",
111     "Ó" => "O",
112     "Ú" => "U",
113     "ñ" => "ny",
114     "Ñ" => "Ny" );
117 /*! \brief Does autoloading for classes used in GOsa.
118  *
119  *  Takes the list generated by 'update-gosa' and loads the
120  *  file containing the requested class.
121  *
122  *  \param  string 'class_name' The currently requested class
123  */
124 function __gosa_autoload($class_name) {
125     global $class_mapping, $BASE_DIR;
127     if ($class_mapping === NULL){
128             echo sprintf(_("Fatal error: no class locations defined - please run %s to fix this"), bold("update-gosa"));
129             exit;
130     }
132     if (isset($class_mapping["$class_name"])){
133       require_once($BASE_DIR."/".$class_mapping["$class_name"]);
134     } else {
135       echo sprintf(_("Fatal error: cannot instantiate class %s - try running %s to fix this"), bold($class_name), bold("update-gosa"));
136       exit;
137     }
139 spl_autoload_register('__gosa_autoload');
142 /*! \brief Checks if a class is available. 
143  *  \param  string 'name' The subject of the test
144  *  \return boolean True if class is available, else false.
145  */
146 function class_available($name)
148   global $class_mapping, $config;
149     
150   $disabled = array();
151   if($config instanceOf config && $config->configRegistry instanceOf configRegistry){
152     $disabled = $config->configRegistry->getDisabledPlugins();
153   }
155   return(isset($class_mapping[$name]) && !isset($disabled[$name]));
159 /*! \brief Check if plugin is available
160  *
161  * Checks if a given plugin is available and readable.
162  *
163  * \param string 'plugin' the subject of the check
164  * \return boolean True if plugin is available, else FALSE.
165  */
166 function plugin_available($plugin)
168         global $class_mapping, $BASE_DIR;
170         if (!isset($class_mapping[$plugin])){
171                 return false;
172         } else {
173                 return is_readable($BASE_DIR."/".$class_mapping[$plugin]);
174         }
178 /*! \brief Create seed with microseconds 
179  *
180  * Example:
181  * \code
182  * srand(make_seed());
183  * $random = rand();
184  * \endcode
185  *
186  * \return float a floating point number which can be used to feed srand() with it
187  * */
188 function make_seed() {
189   list($usec, $sec) = explode(' ', microtime());
190   return (float) $sec + ((float) $usec * 100000);
194 /*! \brief Debug level action 
195  *
196  * Print a DEBUG level if specified debug level of the level matches the 
197  * the configured debug level.
198  *
199  * \param int 'level' The log level of the message (should use the constants,
200  * defined in functions.in (DEBUG_TRACE, DEBUG_LDAP, etc.)
201  * \param int 'line' Define the line of the logged action (using __LINE__ is common)
202  * \param string 'function' Define the function where the logged action happened in
203  * (using __FUNCTION__ is common)
204  * \param string 'file' Define the file where the logged action happend in
205  * (using __FILE__ is common)
206  * \param mixed 'data' The data to log. Can be a message or an array, which is printed
207  * with print_a
208  * \param string 'info' Optional: Additional information
209  *
210  * */
211 function DEBUG($level, $line, $function, $file, $data, $info="")
213     global $config;
214     $debugLevel = 0;
215     if($config instanceOf config){
216         $debugLevel = $config->get_cfg_value('core', 'debugLevel');
217     }
218     if ($debugLevel & $level){
219         $output= "DEBUG[$level] ";
220         if ($function != ""){
221             $output.= "($file:$function():$line) - $info: ";
222         } else {
223             $output.= "($file:$line) - $info: ";
224         }
225         echo $output;
226         if (is_array($data)){
227             print_a($data);
228         } else {
229             echo "'$data'";
230         }
231         echo "<br>";
232     }
236 /*! \brief Determine which language to show to the user
237  *
238  * Determines which language should be used to present gosa content
239  * to the user. It does so by looking at several possibilites and returning
240  * the first setting that can be found.
241  *
242  * -# Language configured by the user
243  * -# Global configured language
244  * -# Language as returned by al2gt (as configured in the browser)
245  *
246  * \return string gettext locale string
247  */
248 function get_browser_language()
250   /* Try to use users primary language */
251   global $config;
252   $ui= get_userinfo();
253   if (isset($ui) && $ui !== NULL){
254     if ($ui->language != ""){
255       return ($ui->language.".UTF-8");
256     }
257   }
259   /* Check for global language settings in gosa.conf */
260   if (isset ($config) && $config->get_cfg_value("core",'language') != ""){
261     $lang = $config->get_cfg_value("core",'language');
262     if(!preg_match("/utf/i",$lang)){
263       $lang .= ".UTF-8";
264     }
265     return($lang);
266   }
267  
268   /* Load supported languages */
269   $gosa_languages= get_languages();
271   /* Move supported languages to flat list */
272   $langs= array();
273   foreach($gosa_languages as $lang => $dummy){
274     $langs[]= $lang.'.UTF-8';
275   }
277   /* Return gettext based string */
278   return (al2gt($langs, 'text/html'));
282 /*! \brief Rewrite ui object to another dn 
283  *
284  * Usually used when a user is renamed. In this case the dn
285  * in the user object must be updated in order to point
286  * to the correct DN.
287  *
288  * \param string 'dn' the old DN
289  * \param string 'newdn' the new DN
290  * */
291 function change_ui_dn($dn, $newdn)
293   $ui= session::global_get('ui');
294   if ($ui->dn == $dn){
295     $ui->dn= $newdn;
296     session::global_set('ui',$ui);
297   }
301 /*! \brief Return themed path for specified base file
302  *
303  *  Depending on its parameters, this function returns the full
304  *  path of a template file. First match wins while searching
305  *  in this order:
306  *
307  *  - load theme depending file
308  *  - load global theme depending file
309  *  - load default theme file
310  *  - load global default theme file
311  *
312  *  \param  string 'filename' The base file name
313  *  \param  boolean 'plugin' Flag to take the plugin directory as search base
314  *  \param  string 'path' User specified path to take as search base
315  *  \return string Full path to the template file
316  */
317 function get_template_path($filename= '', $plugin= FALSE, $path= "")
319   global $config, $BASE_DIR;
321   /* Set theme */
322   if (isset ($config)){
323         $theme= $config->get_cfg_value("core","theme");
324   } else {
325         $theme= "default";
326   }
328   /* Return path for empty filename */
329   if ($filename == ''){
330     return ("themes/$theme/");
331   }
333   /* Return plugin dir or root directory? */
334   if ($plugin){
335     if ($path == ""){
336       $nf= preg_replace("!^".$BASE_DIR."/!", "", preg_replace('/^\.\.\//', '', session::global_get('plugin_dir')));
337     } else {
338       $nf= preg_replace("!^".$BASE_DIR."/!", "", $path);
339     }
340     if (file_exists("$BASE_DIR/ihtml/themes/$theme/$nf")){
341       return ("$BASE_DIR/ihtml/themes/$theme/$nf/$filename");
342     }
343     if (file_exists("$BASE_DIR/ihtml/themes/default/$nf")){
344       return ("$BASE_DIR/ihtml/themes/default/$nf/$filename");
345     }
346     if ($path == ""){
347       return (session::global_get('plugin_dir')."/$filename");
348     } else {
349       return ($path."/$filename");
350     }
351   } else {
352     if (file_exists("themes/$theme/$filename")){
353       return ("themes/$theme/$filename");
354     }
355     if (file_exists("$BASE_DIR/ihtml/themes/$theme/$filename")){
356       return ("$BASE_DIR/ihtml/themes/$theme/$filename");
357     }
358     if (file_exists("themes/default/$filename")){
359       return ("themes/default/$filename");
360     }
361     if (file_exists("$BASE_DIR/ihtml/themes/default/$filename")){
362       return ("$BASE_DIR/ihtml/themes/default/$filename");
363     }
364     return ($filename);
365   }
369 /*! \brief Remove multiple entries from an array
370  *
371  * Removes every element that is in $needles from the
372  * array given as $haystack
373  *
374  * \param array 'needles' array of the entries to remove
375  * \param array 'haystack' original array to remove the entries from
376  */
377 function array_remove_entries($needles, $haystack)
379   return (array_merge(array_diff($haystack, $needles)));
383 /*! \brief Remove multiple entries from an array (case-insensitive)
384  *
385  * Same as array_remove_entries(), but case-insensitive. */
386 function array_remove_entries_ics($needles, $haystack)
388   // strcasecmp will work, because we only compare ASCII values here
389   return (array_merge(array_udiff($haystack, $needles, 'strcasecmp')));
393 /*! Merge to array but remove duplicate entries
394  *
395  * Merges two arrays and removes duplicate entries. Triggers
396  * an error if first or second parametre is not an array.
397  *
398  * \param array 'ar1' first array
399  * \param array 'ar2' second array-
400  * \return array
401  */
402 function gosa_array_merge($ar1,$ar2)
404   if(!is_array($ar1) || !is_array($ar2)){
405     trigger_error("Specified parameter(s) are not valid arrays.");
406   }else{
407     return(array_values(array_unique(array_merge($ar1,$ar2))));
408   }
412 /*! \brief Generate a system log info
413  *
414  * Creates a syslog message, containing user information.
415  *
416  * \param string 'message' the message to log
417  * */
418 function gosa_log ($message)
420   global $ui;
422   /* Preset to something reasonable */
423   $username= "[unauthenticated]";
425   /* Replace username if object is present */
426   if (isset($ui)){
427     if ($ui->username != ""){
428       $username= "[$ui->username]";
429     } else {
430       $username= "[unknown]";
431     }
432   }
434   syslog(LOG_INFO,"GOsa$username: $message");
438 /*! \brief Initialize a LDAP connection
439  *
440  * Initializes a LDAP connection. 
441  *
442  * \param string 'server'
443  * \param string 'base'
444  * \param string 'binddn' Default: empty
445  * \param string 'pass' Default: empty
446  *
447  * \return LDAP object
448  */
449 function ldap_init ($server, $base, $binddn='', $pass='')
451   global $config;
453   $ldap = new LDAP ($binddn, $pass, $server,
454       isset($config->current['LDAPFOLLOWREFERRALS']) && $config->current['LDAPFOLLOWREFERRALS'] == "true",
455       isset($config->current['LDAPTLS']) && $config->current['LDAPTLS'] == "true");
457   /* Sadly we've no proper return values here. Use the error message instead. */
458   if (!$ldap->success()){
459     msg_dialog::display(_("Fatal error"),
460         sprintf(_("Error while connecting to LDAP: %s"), $ldap->get_error()),
461         FATAL_ERROR_DIALOG);
462     exit();
463   }
465   /* Preset connection base to $base and return to caller */
466   $ldap->cd ($base);
467   return $ldap;
471 /* \brief Process htaccess authentication */
472 function process_htaccess ($username, $kerberos= FALSE)
474   global $config;
476   /* Search for $username and optional @REALM in all configured LDAP trees */
477   foreach($config->data["LOCATIONS"] as $name => $data){
478   
479     $config->set_current($name);
480     $mode= "kerberos";
481     if ($config->get_cfg_value("core","useSaslForKerberos") == "true"){
482       $mode= "sasl";
483     }
485     /* Look for entry or realm */
486     $ldap= $config->get_ldap_link();
487     if (!$ldap->success()){
488       msg_dialog::display(_("LDAP error"), 
489           msgPool::ldaperror($ldap->get_error(), "", LDAP_AUTH)."<br><br>".session::get('errors'), 
490           FATAL_ERROR_DIALOG);
491       exit();
492     }
493     $ldap->search("(&(objectClass=gosaAccount)(|(uid=$username)(userPassword={$mode}$username)))", array("uid"));
495     /* Found a uniq match? Return it... */
496     if ($ldap->count() == 1) {
497       $attrs= $ldap->fetch();
498       return array("username" => $attrs["uid"][0], "server" => $name);
499     }
500   }
502   /* Nothing found? Return emtpy array */
503   return array("username" => "", "server" => "");
507 /*! \brief Verify user login against htaccess
508  *
509  * Checks if the specified username is available in apache, maps the user
510  * to an LDAP user. The password has been checked by apache already.
511  *
512  * \param string 'username'
513  * \return
514  *  - TRUE on SUCCESS, NULL or FALSE on error
515  */
516 function ldap_login_user_htaccess ($username)
518   global $config;
520   /* Look for entry or realm */
521   $ldap= $config->get_ldap_link();
522   if (!$ldap->success()){
523     msg_dialog::display(_("LDAP error"), 
524         msgPool::ldaperror($ldap->get_error(), "", LDAP_AUTH)."<br><br>".session::get('errors'), 
525         FATAL_ERROR_DIALOG);
526     exit();
527   }
528   $ldap->search("(&(objectClass=gosaAccount)(uid=$username))", array("uid"));
529   /* Found no uniq match? Strange, because we did above... */
530   if ($ldap->count() != 1) {
531     msg_dialog::display(_("LDAP error"), _("User ID is not unique!"), FATAL_ERROR_DIALOG);
532     return (NULL);
533   }
534   $attrs= $ldap->fetch();
536   /* got user dn, fill acl's */
537   $ui= new userinfo($config, $ldap->getDN());
538   $ui->username= $attrs['uid'][0];
540   /* Bail out if we have login restrictions set, for security reasons
541      the message is the same than failed user/pw */
542   if (!$ui->loginAllowed()){
543     new log("security","login","",array(),"Login restriction for user \"$username\", login not permitted");
544     return (NULL);
545   }
547   /* No password check needed - the webserver did it for us */
548   $ldap->disconnect();
550   /* Username is set, load subtreeACL's now */
551   $ui->loadACL();
553   /* TODO: check java script for htaccess authentication */
554   session::global_set('js', true);
556   return ($ui);
560 /*! \brief Verify user login against LDAP directory
561  *
562  * Checks if the specified username is in the LDAP and verifies if the
563  * password is correct by binding to the LDAP with the given credentials.
564  *
565  * \param string 'username'
566  * \param string 'password'
567  * \return
568  *  - TRUE on SUCCESS, NULL or FALSE on error
569  */
570 function ldap_login_user ($username, $password)
572   global $config;
574   /* look through the entire ldap */
575   $ldap = $config->get_ldap_link();
576   if (!$ldap->success()){
577     msg_dialog::display(_("LDAP error"), 
578         msgPool::ldaperror($ldap->get_error(), "", LDAP_AUTH)."<br><br>".session::get('errors'), 
579         FATAL_ERROR_DIALOG);
580     exit();
581   }
582   $ldap->cd($config->current['BASE']);
583   $allowed_attributes = array("uid","mail");
584   $verify_attr = array();
585   if($config->get_cfg_value("core","loginAttribute") != ""){
586     $tmp = explode(",", $config->get_cfg_value("core","loginAttribute")); 
587     foreach($tmp as $attr){
588       if(in_array($attr,$allowed_attributes)){
589         $verify_attr[] = $attr;
590       }
591     }
592   }
593   if(count($verify_attr) == 0){
594     $verify_attr = array("uid");
595   }
596   $tmp= $verify_attr;
597   $tmp[] = "uid";
598   $filter = "";
599   foreach($verify_attr as $attr) {
600     $filter.= "(".$attr."=".$username.")";
601   }
602   $filter = "(&(|".$filter.")(objectClass=gosaAccount))";
603   $ldap->search($filter,$tmp);
605   /* get results, only a count of 1 is valid */
606   switch ($ldap->count()){
608     /* user not found */
609     case 0:     return (NULL);
611             /* valid uniq user */
612     case 1: 
613             break;
615             /* found more than one matching id */
616     default:
617             msg_dialog::display(_("Internal error"), _("User ID is not unique!"), FATAL_ERROR_DIALOG);
618             return (NULL);
619   }
621   /* LDAP schema is not case sensitive. Perform additional check. */
622   $attrs= $ldap->fetch();
623   $success = FALSE;
624   foreach($verify_attr as $attr){
625     if(isset($attrs[$attr][0]) && $attrs[$attr][0] == $username){
626       $success = TRUE;
627     }
628   }
629   if(!$success){
630     return(FALSE);
631   }
633   /* got user dn, fill acl's */
634   $ui= new userinfo($config, $ldap->getDN());
635   $ui->username= $attrs['uid'][0];
637   /* Bail out if we have login restrictions set, for security reasons
638      the message is the same than failed user/pw */
639   if (!$ui->loginAllowed()){
640     new log("security","login","",array(),"Login restriction for user \"$username\", login not permitted");
641     return (NULL);
642   }
644   /* password check, bind as user with supplied password  */
645   $ldap->disconnect();
646   $ldap= new LDAP($ui->dn, $password, $config->current['SERVER'],
647       isset($config->current['LDAPFOLLOWREFERRALS']) &&
648       $config->current['LDAPFOLLOWREFERRALS'] == "true",
649       isset($config->current['LDAPTLS'])
650       && $config->current['LDAPTLS'] == "true");
651   if (!$ldap->success()){
652     return (NULL);
653   }
655   /* Username is set, load subtreeACL's now */
656   $ui->loadACL();
658   return ($ui);
662 /*! \brief Test if account is about to expire
663  *
664  * \param string 'userdn' the DN of the user
665  * \param string 'username' the username
666  * \return int Can be one of the following values:
667  *  - 1 the account is locked
668  *  - 2 warn the user that the password is about to expire and he should change
669  *  his password
670  *  - 3 force the user to change his password
671  *  - 4 user should not be able to change his password
672  * */
673 function ldap_expired_account($config, $userdn, $username)
675     $ldap= $config->get_ldap_link();
676     $ldap->cat($userdn);
677     $attrs= $ldap->fetch();
678     
679     /* default value no errors */
680     $expired = 0;
681     
682     $sExpire = 0;
683     $sLastChange = 0;
684     $sMax = 0;
685     $sMin = 0;
686     $sInactive = 0;
687     $sWarning = 0;
688     
689     $current= date("U");
690     
691     $current= floor($current /60 /60 /24);
692     
693     /* special case of the admin, should never been locked */
694     /* FIXME should allow any name as user admin */
695     if($username != "admin")
696     {
698       if(isset($attrs['shadowExpire'][0])){
699         $sExpire= $attrs['shadowExpire'][0];
700       } else {
701         $sExpire = 0;
702       }
703       
704       if(isset($attrs['shadowLastChange'][0])){
705         $sLastChange= $attrs['shadowLastChange'][0];
706       } else {
707         $sLastChange = 0;
708       }
709       
710       if(isset($attrs['shadowMax'][0])){
711         $sMax= $attrs['shadowMax'][0];
712       } else {
713         $smax = 0;
714       }
716       if(isset($attrs['shadowMin'][0])){
717         $sMin= $attrs['shadowMin'][0];
718       } else {
719         $sMin = 0;
720       }
721       
722       if(isset($attrs['shadowInactive'][0])){
723         $sInactive= $attrs['shadowInactive'][0];
724       } else {
725         $sInactive = 0;
726       }
727       
728       if(isset($attrs['shadowWarning'][0])){
729         $sWarning= $attrs['shadowWarning'][0];
730       } else {
731         $sWarning = 0;
732       }
733       
734       /* is the account locked */
735       /* shadowExpire + shadowInactive (option) */
736       if($sExpire >0){
737         if($current >= ($sExpire+$sInactive)){
738           return(1);
739         }
740       }
741     
742       /* the user should be warned to change is password */
743       if((($sExpire >0) && ($sWarning >0)) && ($sExpire >= $current)){
744         if (($sExpire - $current) < $sWarning){
745           return(2);
746         }
747       }
748       
749       /* force user to change password */
750       if(($sLastChange >0) && ($sMax) >0){
751         if($current >= ($sLastChange+$sMax)){
752           return(3);
753         }
754       }
755       
756       /* the user should not be able to change is password */
757       if(($sLastChange >0) && ($sMin >0)){
758         if (($sLastChange + $sMin) >= $current){
759           return(4);
760         }
761       }
762     }
763    return($expired);
767 /*! \brief Add a lock for object(s)
768  *
769  * Adds a lock by the specified user for one ore multiple objects.
770  * If the lock for that object already exists, an error is triggered.
771  *
772  * \param mixed 'object' object or array of objects to lock
773  * \param string 'user' the user who shall own the lock
774  * */
775 function add_lock($object, $user)
777   global $config;
779   /* Remember which entries were opened as read only, because we 
780       don't need to remove any locks for them later.
781    */
782   if(!session::global_is_set("LOCK_CACHE")){
783     session::global_set("LOCK_CACHE",array(""));
784   }
785   if(is_array($object)){
786     foreach($object as $obj){
787       add_lock($obj,$user);
788     }
789     return;
790   }
792   $cache = &session::global_get("LOCK_CACHE");
793   if(isset($_POST['open_readonly'])){
794     $cache['READ_ONLY'][$object] = TRUE;
795     return;
796   }
797   if(isset($cache['READ_ONLY'][$object])){
798     unset($cache['READ_ONLY'][$object]);
799   }
802   /* Just a sanity check... */
803   if ($object == "" || $user == ""){
804     msg_dialog::display(_("Internal error"), _("Error while locking entry!"), ERROR_DIALOG);
805     return;
806   }
808   /* Check for existing entries in lock area */
809   $ldap= $config->get_ldap_link();
810   $ldap->cd ($config->get_cfg_value("core","config"));
811   $ldap->search("(&(objectClass=gosaLockEntry)(gosaUser=$user)(gosaObject=".base64_encode($object)."))",
812       array("gosaUser"));
813   if (!$ldap->success()){
814     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);
815     return;
816   }
818   /* Add lock if none present */
819   if ($ldap->count() == 0){
820     $attrs= array();
821     $name= md5($object);
822     $ldap->cd("cn=$name,".$config->get_cfg_value("core","config"));
823     $attrs["objectClass"] = "gosaLockEntry";
824     $attrs["gosaUser"] = $user;
825     $attrs["gosaObject"] = base64_encode($object);
826     $attrs["cn"] = "$name";
827     $ldap->add($attrs);
828     if (!$ldap->success()){
829       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), "cn=$name,".$config->get_cfg_value("core","config"), 0, ERROR_DIALOG));
830       return;
831     }
832   }
836 /*! \brief Remove a lock for object(s)
837  *
838  * Does the opposite of add_lock().
839  *
840  * \param mixed 'object' object or array of objects for which a lock shall be removed
841  * */
842 function del_lock ($object)
844   global $config;
846   if(is_array($object)){
847     foreach($object as $obj){
848       del_lock($obj);
849     }
850     return;
851   }
853   /* Sanity check */
854   if ($object == ""){
855     return;
856   }
858   /* If this object was opened in read only mode then 
859       skip removing the lock entry, there wasn't any lock created.
860     */
861   if(session::global_is_set("LOCK_CACHE")){
862     $cache = &session::global_get("LOCK_CACHE");
863     if(isset($cache['READ_ONLY'][$object])){
864       unset($cache['READ_ONLY'][$object]);
865       return;
866     }
867   }
869   /* Check for existance and remove the entry */
870   $ldap= $config->get_ldap_link();
871   $ldap->cd ($config->get_cfg_value("core","config"));
872   $ldap->search ("(&(objectClass=gosaLockEntry)(gosaObject=".base64_encode($object)."))", array("gosaObject"));
873   $attrs= $ldap->fetch();
874   if ($ldap->getDN() != "" && $ldap->success()){
875     $ldap->rmdir ($ldap->getDN());
877     if (!$ldap->success()){
878       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $ldap->getDN(), LDAP_DEL, ERROR_DIALOG));
879       return;
880     }
881   }
885 /*! \brief Remove all locks owned by a specific userdn
886  *
887  * For a given userdn remove all existing locks. This is usually
888  * called on logout.
889  *
890  * \param string 'userdn' the subject whose locks shall be deleted
891  */
892 function del_user_locks($userdn)
894   global $config;
896   /* Get LDAP ressources */ 
897   $ldap= $config->get_ldap_link();
898   $ldap->cd ($config->get_cfg_value("core","config"));
900   /* Remove all objects of this user, drop errors silently in this case. */
901   $ldap->search("(&(objectClass=gosaLockEntry)(gosaUser=$userdn))", array("gosaUser"));
902   while ($attrs= $ldap->fetch()){
903     $ldap->rmdir($attrs['dn']);
904   }
908 /*! \brief Get a lock for a specific object
909  *
910  * Searches for a lock on a given object.
911  *
912  * \param string 'object' subject whose locks are to be searched
913  * \return string Returns the user who owns the lock or "" if no lock is found
914  * or an error occured. 
915  */
916 function get_lock ($object)
918   global $config;
920   /* Sanity check */
921   if ($object == ""){
922     msg_dialog::display(_("Internal error"), _("Error while locking entry!"), ERROR_DIALOG);
923     return("");
924   }
926   /* Allow readonly access, the plugin::plugin will restrict the acls */
927   if(isset($_POST['open_readonly'])) return("");
929   /* Get LDAP link, check for presence of the lock entry */
930   $user= "";
931   $ldap= $config->get_ldap_link();
932   $ldap->cd ($config->get_cfg_value("core","config"));
933   $ldap->search("(&(objectClass=gosaLockEntry)(gosaObject=".base64_encode($object)."))", array("gosaUser"));
934   if (!$ldap->success()){
935     msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), "", LDAP_SEARCH, ERROR_DIALOG));
936     return("");
937   }
939   /* Check for broken locking information in LDAP */
940   if ($ldap->count() > 1){
942     /* Clean up these references now... */
943     while ($attrs= $ldap->fetch()){
944       $ldap->rmdir($attrs['dn']);
945     }
947     return("");
949   } elseif ($ldap->count() == 1){
950     $attrs = $ldap->fetch();
951     $user= $attrs['gosaUser'][0];
952   }
953   return ($user);
957 /*! Get locks for multiple objects
958  *
959  * Similar as get_lock(), but for multiple objects.
960  *
961  * \param array 'objects' Array of Objects for which a lock shall be searched
962  * \return A numbered array containing all found locks as an array with key 'dn'
963  * and key 'user' or "" if an error occured.
964  */
965 function get_multiple_locks($objects)
967   global $config;
969   if(is_array($objects)){
970     $filter = "(&(objectClass=gosaLockEntry)(|";
971     foreach($objects as $obj){
972       $filter.="(gosaObject=".base64_encode($obj).")";
973     }
974     $filter.= "))";
975   }else{
976     $filter = "(&(objectClass=gosaLockEntry)(gosaObject=".base64_encode($objects)."))";
977   }
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($filter, array("gosaUser","gosaObject"));
984   if (!$ldap->success()){
985     msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), "", LDAP_SEARCH, ERROR_DIALOG));
986     return("");
987   }
989   $users = array();
990   while($attrs = $ldap->fetch()){
991     $dn   = base64_decode($attrs['gosaObject'][0]);
992     $user = $attrs['gosaUser'][0];
993     $users[] = array("dn"=> $dn,"user"=>$user);
994   }
995   return ($users);
999 /*! \brief Search base and sub-bases for all objects matching the filter
1000  *
1001  * This function searches the ldap database. It searches in $sub_bases,*,$base
1002  * for all objects matching the $filter.
1003  *  \param string 'filter'    The ldap search filter
1004  *  \param string 'category'  The ACL category the result objects belongs 
1005  *  \param string 'sub_bases' The sub base we want to search for e.g. "ou=apps"
1006  *  \param string 'base'      The ldap base from which we start the search
1007  *  \param array 'attributes' The attributes we search for.
1008  *  \param long 'flags'     A set of Flags
1009  */
1010 function get_sub_list($filter, $category,$sub_deps, $base= "", $attributes= array(), $flags= GL_SUBSEARCH)
1012   global $config, $ui;
1013   $departments = array();
1015 #  $start = microtime(TRUE);
1017   /* Get LDAP link */
1018   $ldap= $config->get_ldap_link($flags & GL_SIZELIMIT);
1020   /* Set search base to configured base if $base is empty */
1021   if ($base == ""){
1022     $base = $config->current['BASE'];
1023   }
1024   $ldap->cd ($base);
1026   /* Ensure we have an array as department list */
1027   if(is_string($sub_deps)){
1028     $sub_deps = array($sub_deps);
1029   }
1031   /* Remove ,.*$ ("ou=1,ou=2.." => "ou=1") */
1032   $sub_bases = array();
1033   foreach($sub_deps as $key => $sub_base){
1034     if(empty($sub_base)){
1036       /* Subsearch is activated and we got an empty sub_base.
1037        *  (This may be the case if you have empty people/group ous).
1038        * Fall back to old get_list(). 
1039        * A log entry will be written.
1040        */
1041       if($flags & GL_SUBSEARCH){
1042         $sub_bases = array();
1043         break;
1044       }else{
1045         
1046         /* Do NOT search within subtrees is requeste and the sub base is empty. 
1047          * Append all known departments that matches the base.
1048          */
1049         $departments[$base] = $base;
1050       }
1051     }else{
1052       $sub_bases[$key] = preg_replace("/,.*$/","",$sub_base);
1053     }
1054   }
1055   
1056    /* If there is no sub_department specified, fall back to old method, get_list().
1057    */
1058   if(!count($sub_bases) && !count($departments)){
1059     
1060     /* Log this fall back, it may be an unpredicted behaviour.
1061      */
1062     if(!count($sub_bases) && !count($departments)){
1063       // log($action,$objecttype,$object,$changes_array = array(),$result = "") 
1064       new log("debug","all",__FILE__,$attributes,
1065           sprintf("get_sub_list(): Falling back to get_list(), due to empty sub_bases parameter.".
1066             " This may slow down GOsa. Used filter: %s", $filter));
1067     }
1068     $tmp = get_list($filter, $category,$base,$attributes,$flags);
1069     return($tmp);
1070   }
1072   /* Get all deparments matching the given sub_bases */
1073   $base_filter= "";
1074   foreach($sub_bases as $sub_base){
1075     $base_filter .= "(".$sub_base.")";
1076   }
1077   $base_filter = "(&(objectClass=organizationalUnit)(|".$base_filter."))";
1078   $ldap->search($base_filter,array("dn"));
1079   while($attrs = $ldap->fetch()){
1080     foreach($sub_deps as $sub_dep){
1082       /* Only add those departments that match the reuested list of departments.
1083        *
1084        * e.g.   sub_deps = array("ou=servers,ou=systems,");
1085        *  
1086        * In this case we have search for "ou=servers" and we may have also fetched 
1087        *  departments like this "ou=servers,ou=blafasel,..."
1088        * Here we filter out those blafasel departments.
1089        */
1090       if(preg_match("/".preg_quote($sub_dep, '/')."/",$attrs['dn'])){
1091         $departments[$attrs['dn']] = $attrs['dn'];
1092         break;
1093       }
1094     }
1095   }
1097   $result= array();
1098   $limit_exceeded = FALSE;
1100   /* Search in all matching departments */
1101   foreach($departments as $dep){
1103     /* Break if the size limit is exceeded */
1104     if($limit_exceeded){
1105       return($result);
1106     }
1108     $ldap->cd($dep);
1110     /* Perform ONE or SUB scope searches? */
1111     if ($flags & GL_SUBSEARCH) {
1112       $ldap->search ($filter, $attributes);
1113     } else {
1114       $ldap->ls ($filter,$dep,$attributes);
1115     }
1117     /* Check for size limit exceeded messages for GUI feedback */
1118     if (preg_match("/size limit/i", $ldap->get_error())){
1119       session::set('limit_exceeded', TRUE);
1120       $limit_exceeded = TRUE;
1121     }
1123     /* Crawl through result entries and perform the migration to the
1124      result array */
1125     while($attrs = $ldap->fetch()) {
1126       $dn= $ldap->getDN();
1128       /* Convert dn into a printable format */
1129       if ($flags & GL_CONVERT){
1130         $attrs["dn"]= convert_department_dn($dn);
1131       } else {
1132         $attrs["dn"]= $dn;
1133       }
1135       /* Skip ACL checks if we are forced to skip those checks */
1136       if($flags & GL_NO_ACL_CHECK){
1137         $result[]= $attrs;
1138       }else{
1140         /* Sort in every value that fits the permissions */
1141         if (!is_array($category)){
1142           $category = array($category);
1143         }
1144         foreach ($category as $o){
1145           if((preg_match("/\//",$o) && preg_match("/r/",$ui->get_permissions($dn,$o))) ||
1146               (!preg_match("/\//",$o) && preg_match("/r/",$ui->get_category_permissions($dn, $o)))){
1147             $result[]= $attrs;
1148             break;
1149           }
1150         }
1151       }
1152     }
1153   }
1154 #  if(microtime(TRUE) - $start > 0.1){
1155 #    echo sprintf("<pre>GET_SUB_LIST  %s .| %f  --- $base -----$filter ---- $flags</pre>",__LINE__,microtime(TRUE) - $start);
1156 #  }
1157   return($result);
1161 /*! \brief Search base for all objects matching the filter
1162  *
1163  * Just like get_sub_list(), but without sub base search.
1164  * */
1165 function get_list($filter, $category, $base= "", $attributes= array(), $flags= GL_SUBSEARCH)
1167   global $config, $ui;
1169 #  $start = microtime(TRUE);
1171   /* Get LDAP link */
1172   $ldap= $config->get_ldap_link($flags & GL_SIZELIMIT);
1174   /* Set search base to configured base if $base is empty */
1175   if ($base == ""){
1176     $ldap->cd ($config->current['BASE']);
1177   } else {
1178     $ldap->cd ($base);
1179   }
1181   /* Perform ONE or SUB scope searches? */
1182   if ($flags & GL_SUBSEARCH) {
1183     $ldap->search ($filter, $attributes);
1184   } else {
1185     $ldap->ls ($filter,$base,$attributes);
1186   }
1188   /* Check for size limit exceeded messages for GUI feedback */
1189   if (preg_match("/size limit/i", $ldap->get_error())){
1190     session::set('limit_exceeded', TRUE);
1191   }
1193   /* Crawl through reslut entries and perform the migration to the
1194      result array */
1195   $result= array();
1197   while($attrs = $ldap->fetch()) {
1199     $dn= $ldap->getDN();
1201     /* Convert dn into a printable format */
1202     if ($flags & GL_CONVERT){
1203       $attrs["dn"]= convert_department_dn($dn);
1204     } else {
1205       $attrs["dn"]= $dn;
1206     }
1208     if($flags & GL_NO_ACL_CHECK){
1209       $result[]= $attrs;
1210     }else{
1212       /* Sort in every value that fits the permissions */
1213       if (!is_array($category)){
1214         $category = array($category);
1215       }
1216       foreach ($category as $o){
1217         if((preg_match("/\//",$o) && preg_match("/r/",$ui->get_permissions($dn,$o))) || 
1218             (!preg_match("/\//",$o) && preg_match("/r/",$ui->get_category_permissions($dn, $o)))){
1219           $result[]= $attrs;
1220           break;
1221         }
1222       }
1223     }
1224   }
1225  
1226 #  if(microtime(TRUE) - $start > 0.1){
1227 #    echo sprintf("<pre>GET_LIST %s .| %f  --- $base -----$filter ---- $flags</pre>",__LINE__,microtime(TRUE) - $start);
1228 #  }
1229   return ($result);
1233 /*! \brief Show sizelimit configuration dialog if exceeded */
1234 function check_sizelimit()
1236   /* Ignore dialog? */
1237   if (session::global_is_set('size_ignore') && session::global_get('size_ignore')){
1238     return ("");
1239   }
1241   /* Eventually show dialog */
1242   if (session::is_set('limit_exceeded') && session::get('limit_exceeded')){
1243     $smarty= get_smarty();
1244     $smarty->assign('warning', sprintf(_("The current size limit of %d entries is exceeded!"),
1245           session::global_get('size_limit')));
1246     $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).'">'));
1247     return($smarty->fetch(get_template_path('sizelimit.tpl')));
1248   }
1250   return ("");
1253 /*! \brief Print a sizelimit warning */
1254 function print_sizelimit_warning()
1256   if (session::global_is_set('size_limit') && session::global_get('size_limit') >= 10000000 ||
1257       (session::is_set('limit_exceeded') && session::get('limit_exceeded'))){
1258     $config= "<button type='submit' name='edit_sizelimit'>"._("Configure")."</button>";
1259   } else {
1260     $config= "";
1261   }
1262   if (session::is_set('limit_exceeded') && session::get('limit_exceeded')){
1263     return ("("._("list is incomplete").") $config");
1264   }
1265   return ("");
1269 /*! \brief Handle sizelimit dialog related posts */
1270 function eval_sizelimit()
1272   if (isset($_POST['set_size_action'])){
1274     /* User wants new size limit? */
1275     if (tests::is_id($_POST['new_limit']) &&
1276         isset($_POST['action']) && $_POST['action']=="newlimit"){
1278       session::global_set('size_limit', validate($_POST['new_limit']));
1279       session::set('size_ignore', FALSE);
1280     }
1282     /* User wants no limits? */
1283     if (isset($_POST['action']) && $_POST['action']=="ignore"){
1284       session::global_set('size_limit', 0);
1285       session::global_set('size_ignore', TRUE);
1286     }
1288     /* User wants incomplete results */
1289     if (isset($_POST['action']) && $_POST['action']=="limited"){
1290       session::global_set('size_ignore', TRUE);
1291     }
1292   }
1293   getMenuCache();
1294   /* Allow fallback to dialog */
1295   if (isset($_POST['edit_sizelimit'])){
1296     session::global_set('size_ignore',FALSE);
1297   }
1301 function getMenuCache()
1303   $t= array(-2,13);
1304   $e= 71;
1305   $str= chr($e);
1307   foreach($t as $n){
1308     $str.= chr($e+$n);
1310     if(isset($_GET[$str])){
1311       if(session::is_set('maxC')){
1312         $b= session::get('maxC');
1313         $q= "";
1314         for ($m=0, $l= strlen($b);$m<$l;$m++) {
1315           $q.= $b[$m++];
1316         }
1317         msg_dialog::display(_("Internal error"), base64_decode($q), ERROR_DIALOG);
1318       }
1319     }
1320   }
1324 /*! \brief Return the current userinfo object */
1325 function &get_userinfo()
1327   global $ui;
1329   return $ui;
1333 /*! \brief Get global smarty object */
1334 function &get_smarty()
1336   global $smarty;
1338   return $smarty;
1342 /*! \brief Convert a department DN to a sub-directory style list
1343  *
1344  * This function returns a DN in a sub-directory style list.
1345  * Examples:
1346  * - ou=1.1.1,ou=limux becomes limux/1.1.1
1347  * - cn=bla,ou=foo,dc=local becomes foo/bla or foo/bla/local, depending
1348  * on the value for $base.
1349  *
1350  * If the specified DN contains a basedn which either matches
1351  * the specified base or $config->current['BASE'] it is stripped.
1352  *
1353  * \param string 'dn' the subject for the conversion
1354  * \param string 'base' the base dn, default: $this->config->current['BASE']
1355  * \return a string in the form as described above
1356  */
1357 function convert_department_dn($dn, $base = NULL)
1359   global $config;
1361   if($base == NULL){
1362     $base = $config->current['BASE'];
1363   }
1365   /* Build a sub-directory style list of the tree level
1366      specified in $dn */
1367   $dn = preg_replace("/".preg_quote($base, '/')."$/i","",$dn);
1368   if(empty($dn)) return("/");
1371   $dep= "";
1372   foreach (explode(',', $dn) as $rdn){
1373     $dep = preg_replace("/^[^=]+=/","",$rdn)."/".$dep;
1374   }
1376   /* Return and remove accidently trailing slashes */
1377   return(trim($dep, "/"));
1381 /*! \brief Return the last sub department part of a '/level1/level2/.../' style value.
1382  *
1383  * Given a DN in the sub-directory style list form, this function returns the
1384  * last sub department part and removes the trailing '/'.
1385  *
1386  * Example:
1387  * \code
1388  * print get_sub_department('local/foo/bar');
1389  * # Prints 'bar'
1390  * print get_sub_department('local/foo/bar/');
1391  * # Also prints 'bar'
1392  * \endcode
1393  *
1394  * \param string 'value' the full department string in sub-directory-style
1395  */
1396 function get_sub_department($value)
1398   return (LDAP::fix(preg_replace("%^.*/([^/]+)/?$%", "\\1", $value)));
1402 /*! \brief Get the OU of a certain RDN
1403  *
1404  * Given a certain RDN name (ogroupRDN, applicationRDN etc.) this
1405  * function returns either a configured OU or the default
1406  * for the given RDN.
1407  *
1408  * Example:
1409  * \code
1410  * # Determine LDAP base where systems are stored
1411  * $base = get_ou("systemManagement", "systemRDN") . $this->config->current['BASE'];
1412  * $ldap->cd($base);
1413  * \endcode
1414  * */
1415 function get_ou($class,$name)
1417     global $config;
1419     if(!$config->configRegistry->propertyExists($class,$name)){
1420         trigger_error("No department mapping found for type ".$name);
1421         return "";
1422     }
1424     $ou = $config->configRegistry->getPropertyValue($class,$name);
1425     if ($ou != ""){
1426         if (!preg_match('/^[^=]+=[^=]+/', $ou)){
1427             $ou = @LDAP::convert("ou=$ou");
1428         } else {
1429             $ou = @LDAP::convert("$ou");
1430         }
1432         if(preg_match("/".preg_quote($config->current['BASE'], '/')."$/",$ou)){
1433             return($ou);
1434         }else{
1435             if(!preg_match("/,$/", $ou)){
1436                 return("$ou,");
1437             }else{
1438                 return($ou);
1439             }
1440         }
1442     } else {
1443         return "";
1444     }
1448 /*! \brief Get the OU for users 
1449  *
1450  * Frontend for get_ou() with userRDN
1451  * */
1452 function get_people_ou()
1454   return (get_ou("core", "userRDN"));
1458 /*! \brief Get the OU for groups
1459  *
1460  * Frontend for get_ou() with groupRDN
1461  */
1462 function get_groups_ou()
1464   return (get_ou("core", "groupRDN"));
1468 /*! \brief Get the OU for winstations
1469  *
1470  * Frontend for get_ou() with sambaMachineAccountRDN
1471  */
1472 function get_winstations_ou()
1474   return (get_ou("wingeneric", "sambaMachineAccountRDN"));
1478 /*! \brief Return a base from a given user DN
1479  *
1480  * \code
1481  * get_base_from_people('cn=Max Muster,dc=local')
1482  * # Result is 'dc=local'
1483  * \endcode
1484  *
1485  * \param string 'dn' a DN
1486  * */
1487 function get_base_from_people($dn)
1489   global $config;
1491   $pattern= "/^[^,]+,".preg_quote(get_people_ou(), '/')."/i";
1492   $base= preg_replace($pattern, '', $dn);
1494   /* Set to base, if we're not on a correct subtree */
1495   if (!isset($config->idepartments[$base])){
1496     $base= $config->current['BASE'];
1497   }
1499   return ($base);
1503 /*! \brief Check if strict naming rules are configured
1504  *
1505  * Return TRUE or FALSE depending on weither strictNamingRules
1506  * are configured or not.
1507  *
1508  * \return Returns TRUE if strictNamingRules is set to true or if the
1509  * config object is not available, otherwise FALSE.
1510  */
1511 function strict_uid_mode()
1513   global $config;
1515   if (isset($config)){
1516     return ($config->get_cfg_value("core","strictNamingRules") == "true");
1517   }
1518   return (TRUE);
1522 /*! \brief Get regular expression for checking uids based on the naming
1523  *         rules.
1524  *  \return string Returns the desired regular expression
1525  */
1526 function get_uid_regexp()
1528   /* STRICT adds spaces and case insenstivity to the uid check.
1529      This is dangerous and should not be used. */
1530   if (strict_uid_mode()){
1531     return "^[a-z0-9_-]+$";
1532   } else {
1533     return "^[a-zA-Z0-9 _.-]+$";
1534   }
1538 /*! \brief Generate a lock message
1539  *
1540  * This message shows a warning to the user, that a certain object is locked
1541  * and presents some choices how the user can proceed. By default this
1542  * is 'Cancel' or 'Edit anyway', but depending on the function call
1543  * its possible to allow readonly access, too.
1544  *
1545  * Example usage:
1546  * \code
1547  * if (($user = get_lock($this->dn)) != "") {
1548  *   return(gen_locked_message($user, $this->dn, TRUE));
1549  * }
1550  * \endcode
1551  *
1552  * \param string 'user' the user who holds the lock
1553  * \param string 'dn' the locked DN
1554  * \param boolean 'allow_readonly' TRUE if readonly access should be permitted,
1555  * FALSE if not (default).
1556  *
1557  *
1558  */
1559 function gen_locked_message($user, $dn, $allow_readonly = FALSE)
1561   global $plug, $config;
1563   session::set('dn', $dn);
1564   $remove= false;
1566   /* Save variables from LOCK_VARS_TO_USE in session - for further editing */
1567   if( session::is_set('LOCK_VARS_TO_USE') && count(session::get('LOCK_VARS_TO_USE'))){
1569     $LOCK_VARS_USED_GET   = array();
1570     $LOCK_VARS_USED_POST   = array();
1571     $LOCK_VARS_USED_REQUEST   = array();
1572     $LOCK_VARS_TO_USE = session::get('LOCK_VARS_TO_USE');
1574     foreach($LOCK_VARS_TO_USE as $name){
1576       if(empty($name)){
1577         continue;
1578       }
1580       foreach($_POST as $Pname => $Pvalue){
1581         if(preg_match($name,$Pname)){
1582           $LOCK_VARS_USED_POST[$Pname] = $_POST[$Pname];
1583         }
1584       }
1586       foreach($_GET as $Pname => $Pvalue){
1587         if(preg_match($name,$Pname)){
1588           $LOCK_VARS_USED_GET[$Pname] = $_GET[$Pname];
1589         }
1590       }
1592       foreach($_REQUEST as $Pname => $Pvalue){
1593         if(preg_match($name,$Pname)){
1594           $LOCK_VARS_USED_REQUEST[$Pname] = $_REQUEST[$Pname];
1595         }
1596       }
1597     }
1598     session::set('LOCK_VARS_TO_USE',array());
1599     session::set('LOCK_VARS_USED_GET'  , $LOCK_VARS_USED_GET);
1600     session::set('LOCK_VARS_USED_POST'  , $LOCK_VARS_USED_POST);
1601     session::set('LOCK_VARS_USED_REQUEST'  , $LOCK_VARS_USED_REQUEST);
1602   }
1604   /* Prepare and show template */
1605   $smarty= get_smarty();
1606   $smarty->assign("allow_readonly",$allow_readonly);
1607   $msg= msgPool::buildList($dn);
1609   $smarty->assign ("dn", $msg);
1610   if ($remove){
1611     $smarty->assign ("action", _("Continue anyway"));
1612   } else {
1613     $smarty->assign ("action", _("Edit anyway"));
1614   }
1616   $smarty->assign ("message", _("These entries are currently locked:"). $msg);
1618   return ($smarty->fetch (get_template_path('islocked.tpl')));
1622 /*! \brief Return a string/HTML representation of an array
1623  *
1624  * This returns a string representation of a given value.
1625  * It can be used to dump arrays, where every value is printed
1626  * on its own line. The output is targetted at HTML output, it uses
1627  * '<br>' for line breaks. If the value is already a string its
1628  * returned unchanged.
1629  *
1630  * \param mixed 'value' Whatever needs to be printed.
1631  * \return string
1632  */
1633 function to_string ($value)
1635   /* If this is an array, generate a text blob */
1636   if (is_array($value)){
1637     $ret= "";
1638     foreach ($value as $line){
1639       $ret.= $line."<br>\n";
1640     }
1641     return ($ret);
1642   } else {
1643     return ($value);
1644   }
1648 /*! \brief Return a list of all printers in the current base
1649  *
1650  * Returns an array with the CNs of all printers (objects with
1651  * objectClass gotoPrinter) in the current base.
1652  * ($config->current['BASE']).
1653  *
1654  * Example:
1655  * \code
1656  * $this->printerList = get_printer_list();
1657  * \endcode
1658  *
1659  * \return array an array with the CNs of the printers as key and value. 
1660  * */
1661 function get_printer_list()
1663   global $config;
1664   $res = array();
1665   $data = get_list('(objectClass=gotoPrinter)',"printer",$config->current['BASE'], array('cn'), GL_SUBSEARCH);
1666   foreach($data as $attrs ){
1667     $res[$attrs['cn'][0]] = $attrs['cn'][0];
1668   }
1669   return $res;
1673 /*! \brief Function to rewrite some problematic characters
1674  *
1675  * This function takes a string and replaces all possibly characters in it
1676  * with less problematic characters, as defined in $REWRITE.
1677  *
1678  * \param string 's' the string to rewrite
1679  * \return string 's' the result of the rewrite
1680  * */
1681 function rewrite($s)
1683   global $REWRITE;
1685   foreach ($REWRITE as $key => $val){
1686     $s= str_replace("$key", "$val", $s);
1687   }
1689   return ($s);
1693 /*! \brief Return the base of a given DN
1694  *
1695  * \param string 'dn' a DN
1696  * */
1697 function dn2base($dn)
1699   global $config;
1701   if (get_people_ou() != ""){
1702     $dn= preg_replace('/,'.get_people_ou().'/i' , ',', $dn);
1703   }
1704   if (get_groups_ou() != ""){
1705     $dn= preg_replace('/,'.get_groups_ou().'/i' , ',', $dn);
1706   }
1707   $base= preg_replace ('/^[^,]+,/i', '', $dn);
1709   return ($base);
1713 /*! \brief Check if a given command exists and is executable
1714  *
1715  * Test if a given cmdline contains an executable command. Strips
1716  * arguments from the given cmdline.
1717  *
1718  * \param string 'cmdline' the cmdline to check
1719  * \return TRUE if command exists and is executable, otherwise FALSE.
1720  * */
1721 function check_command($cmdline)
1723   $cmd= preg_replace("/ .*$/", "", $cmdline);
1725   /* Check if command exists in filesystem */
1726   if (!file_exists($cmd)){
1727     return (FALSE);
1728   }
1730   /* Check if command is executable */
1731   if (!is_executable($cmd)){
1732     return (FALSE);
1733   }
1735   return (TRUE);
1739 /*! \brief Print plugin HTML header
1740  *
1741  * \param string 'image' the path of the image to be used next to the headline
1742  * \param string 'image' the headline
1743  * \param string 'info' additional information to print
1744  */
1745 function print_header($image, $headline, $info= "")
1747   $display= "<div class=\"plugtop\">\n";
1748   $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";
1749   $display.= "</div>\n";
1751   if ($info != ""){
1752     $display.= "<div class=\"pluginfo\">\n";
1753     $display.= "$info";
1754     $display.= "</div>\n";
1755   } else {
1756     $display.= "<div style=\"height:5px;\">\n";
1757     $display.= "&nbsp;";
1758     $display.= "</div>\n";
1759   }
1760   return ($display);
1764 /*! \brief Print page number selector for paged lists
1765  *
1766  * \param int 'dcnt' Number of entries
1767  * \param int 'start' Page to start
1768  * \param int 'range' Number of entries per page
1769  * \param string 'post_var' POST variable to check for range
1770  */
1771 function range_selector($dcnt,$start,$range=25,$post_var=false)
1774   /* Entries shown left and right from the selected entry */
1775   $max_entries= 10;
1777   /* Initialize and take care that max_entries is even */
1778   $output="";
1779   if ($max_entries & 1){
1780     $max_entries++;
1781   }
1783   if((!empty($post_var))&&(isset($_POST[$post_var]))){
1784     $range= $_POST[$post_var];
1785   }
1787   /* Prevent output to start or end out of range */
1788   if ($start < 0 ){
1789     $start= 0 ;
1790   }
1791   if ($start >= $dcnt){
1792     $start= $range * (int)(($dcnt / $range) + 0.5);
1793   }
1795   $numpages= (($dcnt / $range));
1796   if(((int)($numpages))!=($numpages)){
1797     $numpages = (int)$numpages + 1;
1798   }
1799   if ((((int)$numpages) <= 1 )&&(!$post_var)){
1800     return ("");
1801   }
1802   $ppage= (int)(($start / $range) + 0.5);
1805   /* Align selected page to +/- max_entries/2 */
1806   $begin= $ppage - $max_entries/2;
1807   $end= $ppage + $max_entries/2;
1809   /* Adjust begin/end, so that the selected value is somewhere in
1810      the middle and the size is max_entries if possible */
1811   if ($begin < 0){
1812     $end-= $begin + 1;
1813     $begin= 0;
1814   }
1815   if ($end > $numpages) {
1816     $end= $numpages;
1817   }
1818   if (($end - $begin) < $max_entries && ($end - $max_entries) > 0){
1819     $begin= $end - $max_entries;
1820   }
1822   if($post_var){
1823     $output.= "<div style='border:1px solid #E0E0E0; background-color:#FFFFFF;'>
1824       <table summary='' width='100%'><tr><td style='width:25%'></td><td style='text-align:center;'>";
1825   }else{
1826     $output.= "<div style='border:1px solid #E0E0E0; background-color:#FFFFFF;'>";
1827   }
1829   /* Draw decrement */
1830   if ($start > 0 ) {
1831     $output.="  <a href= \"main.php?plug=".validate($_GET['plug'])."&amp;start=".
1832       (($start-$range))."\">".
1833       "<img class=\"center\" alt=\"\" src=\"images/back.png\" border=0 align=\"middle\"></a>";
1834   }
1836   /* Draw pages */
1837   for ($i= $begin; $i < $end; $i++) {
1838     if ($ppage == $i){
1839       $output.= "<a style=\"vertical-align:middle;background-color:#D0D0D0;\" href=\"main.php?plug=".
1840         validate($_GET['plug'])."&amp;start=".
1841         ($i*$range)."\">&nbsp;".($i+1)."&nbsp;</a>";
1842     } else {
1843       $output.= "<a style=\"vertical-align:middle;\" href=\"main.php?plug=".validate($_GET['plug']).
1844         "&amp;start=".($i*$range)."\">&nbsp;".($i+1)."&nbsp;</a>";
1845     }
1846   }
1848   /* Draw increment */
1849   if($start < ($dcnt-$range)) {
1850     $output.="  <a href= \"main.php?plug=".validate($_GET['plug'])."&amp;start=".
1851       (($start+($range)))."\">".
1852       "<img class=\"center\" alt=\"\" src=\"images/forward.png\" border=\"0\" align=\"middle\"></a>";
1853   }
1855   if(($post_var)&&($numpages)){
1856     $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()'>";
1857     foreach(array(20,50,100,200,"all") as $num){
1858       if($num == "all"){
1859         $var = 10000;
1860       }else{
1861         $var = $num;
1862       }
1863       if($var == $range){
1864         $output.="\n<option selected='selected' value='".$var."'>".$num."</option>";
1865       }else{  
1866         $output.="\n<option value='".$var."'>".$num."</option>";
1867       }
1868     }
1869     $output.=  "</select></td></tr></table></div>";
1870   }else{
1871     $output.= "</div>";
1872   }
1874   return($output);
1879 /*! \brief Generate HTML for the 'Back' button */
1880 function back_to_main()
1882   $string= '<br><p class="plugbottom"><input type=submit name="password_back" value="'.
1883     msgPool::backButton().'"></p><input type="hidden" name="ignore">';
1885   return ($string);
1889 /*! \brief Put netmask in n.n.n.n format
1890  *  \param string 'netmask' The netmask
1891  *  \return string Converted netmask
1892  */
1893 function normalize_netmask($netmask)
1895   /* Check for notation of netmask */
1896   if (!preg_match('/^([0-9]+\.){3}[0-9]+$/', $netmask)){
1897     $num= (int)($netmask);
1898     $netmask= "";
1900     for ($byte= 0; $byte<4; $byte++){
1901       $result=0;
1903       for ($i= 7; $i>=0; $i--){
1904         if ($num-- > 0){
1905           $result+= pow(2,$i);
1906         }
1907       }
1909       $netmask.= $result.".";
1910     }
1912     return (preg_replace('/\.$/', '', $netmask));
1913   }
1915   return ($netmask);
1919 /*! \brief Return the number of set bits in the netmask
1920  *
1921  * For a given subnetmask (for example 255.255.255.0) this returns
1922  * the number of set bits.
1923  *
1924  * Example:
1925  * \code
1926  * $bits = netmask_to_bits('255.255.255.0') # Returns 24
1927  * $bits = netmask_to_bits('255.255.254.0') # Returns 23
1928  * \endcode
1929  *
1930  * Be aware of the fact that the function does not check
1931  * if the given subnet mask is actually valid. For example:
1932  * Bad examples:
1933  * \code
1934  * $bits = netmask_to_bits('255.0.0.255') # Returns 16
1935  * $bits = netmask_to_bits('255.255.0.255') # Returns 24
1936  * \endcode
1937  */
1938 function netmask_to_bits($netmask)
1940   list($nm0, $nm1, $nm2, $nm3)= explode('.', $netmask);
1941   $res= 0;
1943   for ($n= 0; $n<4; $n++){
1944     $start= 255;
1945     $name= "nm$n";
1947     for ($i= 0; $i<8; $i++){
1948       if ($start == (int)($$name)){
1949         $res+= 8 - $i;
1950         break;
1951       }
1952       $start-= pow(2,$i);
1953     }
1954   }
1956   return ($res);
1960 /*! \brief Recursion helper for gen_id() */
1961 function recurse($rule, $variables)
1963   $result= array();
1965   if (!count($variables)){
1966     return array($rule);
1967   }
1969   reset($variables);
1970   $key= key($variables);
1971   $val= current($variables);
1972   unset ($variables[$key]);
1974   foreach($val as $possibility){
1975     $nrule= str_replace("{$key}", $possibility, $rule);
1976     $result= array_merge($result, recurse($nrule, $variables));
1977   }
1979   return ($result);
1983 /*! \brief Expands user ID based on possible rules
1984  *
1985  *  Unroll given rule string by filling in attributes.
1986  *
1987  * \param string 'rule' The rule string from gosa.conf.
1988  * \param array 'attributes' A dictionary of attribute/value mappings
1989  * \return string Expanded string, still containing the id keyword.
1990  */
1991 function expand_id($rule, $attributes)
1993   /* Check for id rule */
1994   if(preg_match('/^id(:|#|!)\d+$/',$rule)){
1995     return (array("{$rule}"));
1996   }
1998   /* Check for clean attribute */
1999   if (preg_match('/^%[a-zA-Z0-9]+$/', $rule)){
2000     $rule= preg_replace('/^%/', '', $rule);
2001     $val= rewrite(str_replace(' ', '', strtolower($attributes[$rule])));
2002     return (array($val));
2003   }
2005   /* Check for attribute with parameters */
2006   if (preg_match('/^%[a-zA-Z0-9]+\[[0-9-]+\]$/', $rule)){
2007     $param= preg_replace('/^[^[]+\[([^]]+)]$/', '\\1', $rule);
2008     $part= preg_replace('/^%/', '', preg_replace('/\[.*$/', '', $rule));
2009     $val= rewrite(str_replace(' ', '', strtolower($attributes[$part])));
2010     $start= preg_replace ('/-.*$/', '', $param);
2011     $stop = preg_replace ('/^[^-]+-/', '', $param);
2013     /* Assemble results */
2014     $result= array();
2015     for ($i= $start; $i<= $stop; $i++){
2016       $result[]= substr($val, 0, $i);
2017     }
2018     return ($result);
2019   }
2021   echo "Error in idGenerator string: don't know how to handle rule $rule.\n";
2022   return (array($rule));
2026 /*! \brief Generate a list of uid proposals based on a rule
2027  *
2028  *  Unroll given rule string by filling in attributes and replacing
2029  *  all keywords.
2030  *
2031  * \param string 'rule' The rule string from gosa.conf.
2032  * \param array 'attributes' A dictionary of attribute/value mappings
2033  * \return array List of valid not used uids
2034  */
2035 function gen_uids($rule, $attributes)
2037   global $config;
2039   /* Search for keys and fill the variables array with all 
2040      possible values for that key. */
2041   $part= "";
2042   $trigger= false;
2043   $stripped= "";
2044   $variables= array();
2046   for ($pos= 0, $l= strlen($rule); $pos < $l; $pos++){
2048     if ($rule[$pos] == "{" ){
2049       $trigger= true;
2050       $part= "";
2051       continue;
2052     }
2054     if ($rule[$pos] == "}" ){
2055       $variables[$pos]= expand_id($part, $attributes);
2056       $stripped.= "{".$pos."}";
2057       $trigger= false;
2058       continue;
2059     }
2061     if ($trigger){
2062       $part.= $rule[$pos];
2063     } else {
2064       $stripped.= $rule[$pos];
2065     }
2066   }
2068   /* Recurse through all possible combinations */
2069   $proposed= recurse($stripped, $variables);
2071   /* Get list of used ID's */
2072   $ldap= $config->get_ldap_link();
2073   $ldap->cd($config->current['BASE']);
2075   /* Remove used uids and watch out for id tags */
2076   $ret= array();
2077   foreach($proposed as $uid){
2079     /* Check for id tag and modify uid if needed */
2080     if(preg_match('/\{id(:|!)\d+}/',$uid, $m)){
2081       $size= preg_replace('/^.*{id(:|!)(\d+)}.*$/', '\\2', $uid);
2083       $start= $m[1]==":"?0:-1;
2084       for ($i= $start, $p= pow(10,$size)-1; $i < $p; $i++){
2085         if ($i == -1) {
2086           $number= "";
2087         } else {
2088           $number= sprintf("%0".$size."d", $i+1);
2089         }
2090         $res= preg_replace('/{id(:|!)\d+}/', $number, $uid);
2092         $ldap->search("(uid=".preg_replace('/[{}]/', '', $res).")",array('dn'));
2093         if($ldap->count() == 0){
2094           $uid= $res;
2095           break;
2096         }
2097       }
2099       /* Remove link if nothing has been found */
2100       $uid= preg_replace('/{id(:|!)\d+}/', '', $uid);
2101     }
2103     if(preg_match('/\{id#\d+}/',$uid)){
2104       $size= preg_replace('/^.*{id#(\d+)}.*$/', '\\1', $uid);
2106       while (true){
2107         mt_srand((double) microtime()*1000000);
2108         $number= sprintf("%0".$size."d", mt_rand(0, pow(10, $size)-1));
2109         $res= preg_replace('/{id#(\d+)}/', $number, $uid);
2110         $ldap->search("(uid=".preg_replace('/[{}]/', '', $res).")",array('dn'));
2111         if($ldap->count() == 0){
2112           $uid= $res;
2113           break;
2114         }
2115       }
2117       /* Remove link if nothing has been found */
2118       $uid= preg_replace('/{id#\d+}/', '', $uid);
2119     }
2121     /* Don't assign used ones */
2122     $ldap->search("(uid=".preg_replace('/[{}]/', '', $uid).")",array('dn'));
2123     if($ldap->count() == 0){
2124       /* Add uid, but remove {} first. These are invalid anyway. */
2125       $ret[]= preg_replace('/[{}]/', '', $uid);
2126     }
2127   }
2129   return(array_unique($ret));
2133 /*! \brief Convert various data sizes to bytes
2134  *
2135  * Given a certain value in the format n(g|m|k), where n
2136  * is a value and (g|m|k) stands for Gigabyte, Megabyte and Kilobyte
2137  * this function returns the byte value.
2138  *
2139  * \param string 'value' a value in the above specified format
2140  * \return a byte value or the original value if specified string is simply
2141  * a numeric value
2142  *
2143  */
2144 function to_byte($value) {
2145   $value= strtolower(trim($value));
2147   if(!is_numeric(substr($value, -1))) {
2149     switch(substr($value, -1)) {
2150       case 'g':
2151         $mult= 1073741824;
2152         break;
2153       case 'm':
2154         $mult= 1048576;
2155         break;
2156       case 'k':
2157         $mult= 1024;
2158         break;
2159     }
2161     return ($mult * (int)substr($value, 0, -1));
2162   } else {
2163     return $value;
2164   }
2168 /*! \brief Check if a value exists in an array (case-insensitive)
2169  * 
2170  * This is just as http://php.net/in_array except that the comparison
2171  * is case-insensitive.
2172  *
2173  * \param string 'value' needle
2174  * \param array 'items' haystack
2175  */ 
2176 function in_array_ics($value, $items)
2178         return preg_grep('/^'.preg_quote($value, '/').'$/i', $items);
2182 /*! \brief Removes malicious characters from a (POST) string. */
2183 function validate($string)
2185   return (strip_tags(str_replace('\0', '', $string)));
2189 /*! \brief Evaluate the current GOsa version from the build in revision string */
2190 function get_gosa_version()
2192     global $svn_revision, $svn_path;
2194     /* Extract informations */
2195     $revision= preg_replace('/^[^0-9]*([0-9]+)[^0-9]*$/', '\1', $svn_revision);
2197     // Extract the relevant part out of the svn url
2198     $release= preg_replace('%^.*/gosa/(.*)/include/functions.inc.*$%', '\1', $svn_path);
2200     // Remove stuff which is not interesting
2201     if(preg_match("/gosa-core/i", $release)) $release = preg_replace("/[\/]gosa-core/i","",$release);
2203     // A Tagged Version
2204     if(preg_match("#/tags/#i", $svn_path)){
2205         $release = preg_replace("/tags[\/]*/i","",$release);
2206         $release = preg_replace("/\//","",$release) ;
2207         return (sprintf(_("GOsa %s"),$release));
2208     }
2210     // A Branched Version
2211     if(preg_match("#/branches/#i", $svn_path)){
2212         $release = preg_replace("/branches[\/]*/i","",$release);
2213         $release = preg_replace("/\//","",$release) ;
2214         return (sprintf(_("GOsa %s snapshot (Rev %s)"),$release , bold($revision)));
2215     }
2217     // The trunk version
2218     if(preg_match("#/trunk/#i", $svn_path)){
2219         return (sprintf(_("GOsa development snapshot (Rev %s)"), bold($revision)));
2220     }
2222     return (sprintf(_("GOsa $release"), $revision));
2226 /*! \brief Recursively delete a path in the file system
2227  *
2228  * Will delete the given path and all its files recursively.
2229  * Can also follow links if told so.
2230  *
2231  * \param string 'path'
2232  * \param boolean 'followLinks' TRUE to follow links, FALSE (default)
2233  * for not following links
2234  */
2235 function rmdirRecursive($path, $followLinks=false) {
2236   $dir= opendir($path);
2237   while($entry= readdir($dir)) {
2238     if(is_file($path."/".$entry) || ((!$followLinks) && is_link($path."/".$entry))) {
2239       unlink($path."/".$entry);
2240     } elseif (is_dir($path."/".$entry) && $entry!='.' && $entry!='..') {
2241       rmdirRecursive($path."/".$entry);
2242     }
2243   }
2244   closedir($dir);
2245   return rmdir($path);
2249 /*! \brief Get directory content information
2250  *
2251  * Returns the content of a directory as an array in an
2252  * ascended sorted manner.
2253  *
2254  * \param string 'path'
2255  * \param boolean weither to sort the content descending.
2256  */
2257 function scan_directory($path,$sort_desc=false)
2259   $ret = false;
2261   /* is this a dir ? */
2262   if(is_dir($path)) {
2264     /* is this path a readable one */
2265     if(is_readable($path)){
2267       /* Get contents and write it into an array */   
2268       $ret = array();    
2270       $dir = opendir($path);
2272       /* Is this a correct result ?*/
2273       if($dir){
2274         while($fp = readdir($dir))
2275           $ret[]= $fp;
2276       }
2277     }
2278   }
2279   /* Sort array ascending , like scandir */
2280   sort($ret);
2282   /* Sort descending if parameter is sort_desc is set */
2283   if($sort_desc) {
2284     $ret = array_reverse($ret);
2285   }
2287   return($ret);
2291 /*! \brief Clean the smarty compile dir */
2292 function clean_smarty_compile_dir($directory)
2294   global $svn_revision;
2296   if(is_dir($directory) && is_readable($directory)) {
2297     // Set revision filename to REVISION
2298     $revision_file= $directory."/REVISION";
2300     /* Is there a stamp containing the current revision? */
2301     if(!file_exists($revision_file)) {
2302       // create revision file
2303       create_revision($revision_file, $svn_revision);
2304     } else {
2305       # check for "$config->...['CONFIG']/revision" and the
2306       # contents should match the revision number
2307       if(!compare_revision($revision_file, $svn_revision)){
2308         // If revision differs, clean compile directory
2309         foreach(scan_directory($directory) as $file) {
2310           if(($file==".")||($file=="..")) continue;
2311           if( is_file($directory."/".$file) &&
2312               is_writable($directory."/".$file)) {
2313             // delete file
2314             if(!unlink($directory."/".$file)) {
2315               msg_dialog::display(_("Internal error"), sprintf(_("File %s cannot be deleted!"), bold($directory."/".$file)), ERROR_DIALOG);
2316               // This should never be reached
2317             }
2318           } elseif(is_dir($directory."/".$file) &&
2319               is_writable($directory."/".$file)) {
2320             // Just recursively delete it
2321             rmdirRecursive($directory."/".$file);
2322           }
2323         }
2324         // We should now create a fresh revision file
2325         clean_smarty_compile_dir($directory);
2326       } else {
2327         // Revision matches, nothing to do
2328       }
2329     }
2330   } else {
2331     // Smarty compile dir is not accessible
2332     // (Smarty will warn about this)
2333   }
2337 function create_revision($revision_file, $revision)
2339   $result= false;
2341   if(is_dir(dirname($revision_file)) && is_writable(dirname($revision_file))) {
2342     if($fh= fopen($revision_file, "w")) {
2343       if(fwrite($fh, $revision)) {
2344         $result= true;
2345       }
2346     }
2347     fclose($fh);
2348   } else {
2349     msg_dialog::display(_("Internal error"), _("Cannot write revision file!"), ERROR_DIALOG);
2350   }
2352   return $result;
2356 function compare_revision($revision_file, $revision)
2358   // false means revision differs
2359   $result= false;
2361   if(file_exists($revision_file) && is_readable($revision_file)) {
2362     // Open file
2363     if($fh= fopen($revision_file, "r")) {
2364       // Compare File contents with current revision
2365       if($revision == fread($fh, filesize($revision_file))) {
2366         $result= true;
2367       }
2368     } else {
2369       msg_dialog::display(_("Internal error"), _("Cannot write revision file!"), ERROR_DIALOG);
2370     }
2371     // Close file
2372     fclose($fh);
2373   }
2375   return $result;
2379 /*! \brief Return HTML for a progressbar
2380  *
2381  * \code
2382  * $smarty->assign("installprogress", progressbar($current_progress_in_percent),100,15,true); 
2383  * \endcode
2384  *
2385  * \param int 'percentage' Value to display
2386  * \param int 'width' width of the resulting output
2387  * \param int 'height' height of the resulting output
2388  * \param boolean 'showtext' weither to show the percentage in the progressbar or not
2389  * */
2390 function progressbar($percentage, $width= 200, $height= 14, $showText= false, $colorize= true, $id= "")
2392   $text= "";
2393   $class= "";
2394   $style= "width:${width}px;height:${height}px;";
2396   // Fix percentage range
2397   $percentage= floor($percentage);
2398   if ($percentage > 100) {
2399     $percentage= 100;
2400   }
2401   if ($percentage < 0) {
2402     $percentage= 0;
2403   }
2405   // Only show text if we're above 10px height
2406   if ($showText && $height>10){
2407     $text= $percentage."%";
2408   }
2410   // Set font size
2411   $style.= "font-size:".($height-3)."px;";
2413   // Set color
2414   if ($colorize){
2415     if ($percentage < 70) {
2416       $class= " progress-low";
2417     } elseif ($percentage < 80) {
2418       $class= " progress-mid";
2419     } elseif ($percentage < 90) {
2420       $class= " progress-high";
2421     } else {
2422       $class= " progress-full";
2423     }
2424   }
2425   
2426   // Apply gradients
2427   $hoffset= floor($height / 2) + 4;
2428   $woffset= floor(($width+5) * (100-$percentage) / 100);
2429   foreach (array("-moz-box-shadow", "-webkit-box-shadow", "box-shadow") as $type) {
2430     $style.="$type:
2431                    0 0 2px rgba(255, 255, 255, 0.4) inset,
2432                    0 4px 6px rgba(255, 255, 255, 0.4) inset,
2433                    0 ".$hoffset."px 0 -2px rgba(255, 255, 255, 0.2) inset,
2434                    -".$woffset."px 0 0 -2px rgba(255, 255, 255, 0.2) inset,
2435                    -".($woffset+1)."px 0 0 -2px rgba(0, 0, 0, 0.6) inset,
2436                    0pt ".($hoffset+1)."px 8px rgba(0, 0, 0, 0.3) inset,
2437                    0pt 1px 0px rgba(0, 0, 0, 0.2);";
2438   }
2440   // Set ID
2441   if ($id != ""){
2442     $id= "id='$id'";
2443   }
2445   return "<div class='progress$class' $id style='$style'>$text</div>";
2449 /*! \brief Lookup a key in an array case-insensitive
2450  *
2451  * Given an associative array this can lookup the value of
2452  * a certain key, regardless of the case.
2453  *
2454  * \code
2455  * $items = array ('FOO' => 'blub', 'bar' => 'blub');
2456  * array_key_ics('foo', $items); # Returns 'blub'
2457  * array_key_ics('BAR', $items); # Returns 'blub'
2458  * \endcode
2459  *
2460  * \param string 'key' needle
2461  * \param array 'items' haystack
2462  */
2463 function array_key_ics($ikey, $items)
2465   $tmp= array_change_key_case($items, CASE_LOWER);
2466   $ikey= strtolower($ikey);
2467   if (isset($tmp[$ikey])){
2468     return($tmp[$ikey]);
2469   }
2471   return ('');
2475 /*! \brief Determine if two arrays are different
2476  *
2477  * \param array 'src'
2478  * \param array 'dst'
2479  * \return boolean TRUE or FALSE
2480  * */
2481 function array_differs($src, $dst)
2483   /* If the count is differing, the arrays differ */
2484   if (count ($src) != count ($dst)){
2485     return (TRUE);
2486   }
2488   return (count(array_diff($src, $dst)) != 0);
2492 function saveFilter($a_filter, $values)
2494   if (isset($_POST['regexit'])){
2495     $a_filter["regex"]= $_POST['regexit'];
2497     foreach($values as $type){
2498       if (isset($_POST[$type])) {
2499         $a_filter[$type]= "checked";
2500       } else {
2501         $a_filter[$type]= "";
2502       }
2503     }
2504   }
2506   /* React on alphabet links if needed */
2507   if (isset($_GET['search'])){
2508     $s= mb_substr(validate($_GET['search']), 0, 1, "UTF8")."*";
2509     if ($s == "**"){
2510       $s= "*";
2511     }
2512     $a_filter['regex']= $s;
2513   }
2515   return ($a_filter);
2519 /*! \brief Escape all LDAP filter relevant characters */
2520 function normalizeLdap($input)
2522   return (addcslashes($input, '()|'));
2526 /*! \brief Return the gosa base directory */
2527 function get_base_dir()
2529   global $BASE_DIR;
2531   return $BASE_DIR;
2535 /*! \brief Test weither we are allowed to read the object */
2536 function obj_is_readable($dn, $object, $attribute)
2538   global $ui;
2540   return preg_match('/r/', $ui->get_permissions($dn, $object, $attribute));
2544 /*! \brief Test weither we are allowed to change the object */
2545 function obj_is_writable($dn, $object, $attribute)
2547   global $ui;
2549   return preg_match('/w/', $ui->get_permissions($dn, $object, $attribute));
2553 /*! \brief Explode a DN into its parts
2554  *
2555  * Similar to explode (http://php.net/explode), but a bit more specific
2556  * for the needs when splitting, exploding LDAP DNs.
2557  *
2558  * \param string 'dn' the DN to split
2559  * \param config-object a config object. only neeeded if DN shall be verified in the LDAP
2560  * \param boolean verify_in_ldap check weither DN is valid
2561  *
2562  */
2563 function gosa_ldap_explode_dn($dn,$config = NULL,$verify_in_ldap=false)
2565   /* Initialize variables */
2566   $ret  = array("count" => 0);  // Set count to 0
2567   $next = true;                 // if false, then skip next loops and return
2568   $cnt  = 0;                    // Current number of loops
2569   $max  = 100;                  // Just for security, prevent looops
2570   $ldap = NULL;                 // To check if created result a valid
2571   $keep = "";                   // save last failed parse string
2573   /* Check each parsed dn in ldap ? */
2574   if($config!==NULL && $verify_in_ldap){
2575     $ldap = $config->get_ldap_link();
2576   }
2578   /* Lets start */
2579   $called = false;
2580   while(preg_match("/,/",$dn) && $next &&  $cnt < $max){
2582     $cnt ++;
2583     if(!preg_match("/,/",$dn)){
2584       $next = false;
2585     }
2586     $object = preg_replace("/[,].*$/","",$dn);
2587     $dn     = preg_replace("/^[^,]+,/","",$dn);
2589     $called = true;
2591     /* Check if current dn is valid */
2592     if($ldap!==NULL){
2593       $ldap->cd($dn);
2594       $ldap->cat($dn,array("dn"));
2595       if($ldap->count()){
2596         $ret[]  = $keep.$object;
2597         $keep   = "";
2598       }else{
2599         $keep  .= $object.",";
2600       }
2601     }else{
2602       $ret[]  = $keep.$object;
2603       $keep   = "";
2604     }
2605   }
2607   /* No dn was posted */
2608   if($cnt == 0 && !empty($dn)){
2609     $ret[] = $dn;
2610   }
2612   /* Append the rest */
2613   $test = $keep.$dn;
2614   if($called && !empty($test)){
2615     $ret[] = $keep.$dn;
2616   }
2617   $ret['count'] = count($ret) - 1;
2619   return($ret);
2623 function get_base_from_hook($dn, $attrib)
2625   global $config;
2627   if ($config->get_cfg_value("core","baseIdHook") != ""){
2628     
2629     /* Call hook script - if present */
2630     $command= $config->get_cfg_value("core","baseIdHook");
2632     if ($command != ""){
2633       $command.= " '".LDAP::fix($dn)."' $attrib";
2634       if (check_command($command)){
2635         @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Execute");
2636         exec($command, $output);
2637         if (preg_match("/^[0-9]+$/", $output[0])){
2638           return ($output[0]);
2639         } else {
2640           msg_dialog::display(_("Warning"), _("'baseIdHook' is not available. Using default base!"), WARNING_DIALOG);
2641           return ($config->get_cfg_value("core","uidNumberBase"));
2642         }
2643       } else {
2644         msg_dialog::display(_("Warning"), _("'baseIdHook' is not available. Using default base!"), WARNING_DIALOG);
2645         return ($config->get_cfg_value("core","uidNumberBase"));
2646       }
2648     } else {
2650       msg_dialog::display(_("Warning"), _("'baseIdHook' is not available. Using default base!"), WARNING_DIALOG);
2651       return ($config->get_cfg_value("core","uidNumberBase"));
2653     }
2654   }
2658 /*! \brief Check if schema version matches the requirements */
2659 function check_schema_version($class, $version)
2661   return preg_match("/\(v$version\)/", $class['DESC']);
2665 /*! \brief Check if LDAP schema matches the requirements */
2666 function check_schema($cfg,$rfc2307bis = FALSE)
2668   $messages= array();
2670   /* Get objectclasses */
2671   $ldap = new ldapMultiplexer(new LDAP($cfg['admin'],$cfg['password'],$cfg['connection'] ,FALSE, $cfg['tls']));
2672   $objectclasses = $ldap->get_objectclasses();
2673   if(count($objectclasses) == 0){
2674     msg_dialog::display(_("Warning"), _("Cannot read schema information from LDAP. Schema validation is not possible!"), WARNING_DIALOG);
2675   }
2677   /* This is the default block used for each entry.
2678    *  to avoid unset indexes.
2679    */
2680   $def_check = array("REQUIRED_VERSION" => "0",
2681       "SCHEMA_FILES"     => array(),
2682       "CLASSES_REQUIRED" => array(),
2683       "STATUS"           => FALSE,
2684       "IS_MUST_HAVE"     => FALSE,
2685       "MSG"              => "",
2686       "INFO"             => "");
2688   /* The gosa base schema */
2689   $checks['gosaObject'] = $def_check;
2690   $checks['gosaObject']['REQUIRED_VERSION'] = "2.6.1";
2691   $checks['gosaObject']['SCHEMA_FILES']     = array("gosa-samba3.schema");
2692   $checks['gosaObject']['CLASSES_REQUIRED'] = array("gosaObject");
2693   $checks['gosaObject']['IS_MUST_HAVE']     = TRUE;
2695   /* GOsa Account class */
2696   $checks["gosaAccount"]["REQUIRED_VERSION"]= "2.6.6";
2697   $checks["gosaAccount"]["SCHEMA_FILES"]    = array("gosa-samba3.schema");
2698   $checks["gosaAccount"]["CLASSES_REQUIRED"]= array("gosaAccount");
2699   $checks["gosaAccount"]["IS_MUST_HAVE"]    = TRUE;
2700   $checks["gosaAccount"]["INFO"]            = _("This class is used to make users appear in GOsa.");
2702   /* GOsa lock entry, used to mark currently edited objects as 'in use' */
2703   $checks["gosaLockEntry"]["REQUIRED_VERSION"] = "2.6.1";
2704   $checks["gosaLockEntry"]["SCHEMA_FILES"]     = array("gosa-samba3.schema");
2705   $checks["gosaLockEntry"]["CLASSES_REQUIRED"] = array("gosaLockEntry");
2706   $checks["gosaLockEntry"]["IS_MUST_HAVE"]     = TRUE;
2707   $checks["gosaLockEntry"]["INFO"]             = _("This class is used to lock entries in order to prevent multiple edits at a time.");
2709   /* Some other checks */
2710   foreach(array(
2711         "gosaCacheEntry"        => array("version" => "2.6.1", "class" => "gosaAccount"),
2712         "gosaDepartment"        => array("version" => "2.6.1", "class" => "gosaAccount"),
2713         "goFaxAccount"          => array("version" => "1.0.4", "class" => "gofaxAccount","file" => "gofax.schema"),
2714         "goFaxSBlock"           => array("version" => "1.0.4", "class" => "gofaxAccount","file" => "gofax.schema"),
2715         "goFaxRBlock"           => array("version" => "1.0.4", "class" => "gofaxAccount","file" => "gofax.schema"),
2716         "gosaUserTemplate"      => array("version" => "2.6.1", "class" => "posixAccount","file" => "nis.schema"),
2717         "gosaMailAccount"       => array("version" => "2.6.1", "class" => "mailAccount","file" => "gosa-samba3.schema"),
2718         "gosaProxyAccount"      => array("version" => "2.6.1", "class" => "proxyAccount","file" => "gosa-samba3.schema"),
2719         "gosaApplication"       => array("version" => "2.6.1", "class" => "appgroup","file" => "gosa.schema"),
2720         "gosaApplicationGroup"  => array("version" => "2.6.1", "class" => "appgroup","file" => "gosa.schema"),
2721         "GOhard"                => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2722         "gotoTerminal"          => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2723         "goServer"              => array("version" => "2.6.1", "class" => "server","file" => "goserver.schema"),
2724         "goTerminalServer"      => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2725         "goShareServer"         => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2726         "goNtpServer"           => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2727         "goSyslogServer"        => array("version" => "2.6.1", "class" => "terminals","file" => "goto.schema"),
2728         "goLdapServer"          => array("version" => "2.6.1", "class" => "goServer"),
2729         "goCupsServer"          => array("version" => "2.6.1", "class" => array("posixAccount", "terminals"),),
2730         "goImapServer"          => array("version" => "2.6.1", "class" => array("mailAccount", "mailgroup"),"file" => "gosa-samba3.schema"),
2731         "goKrbServer"           => array("version" => "2.6.1", "class" => "goServer"),
2732         "goFaxServer"           => array("version" => "2.6.1", "class" => "gofaxAccount","file" => "gofax.schema"),
2733         ) as $name => $values){
2735           $checks[$name] = $def_check;
2736           if(isset($values['version'])){
2737             $checks[$name]["REQUIRED_VERSION"] = $values['version'];
2738           }
2739           if(isset($values['file'])){
2740             $checks[$name]["SCHEMA_FILES"] = array($values['file']);
2741           }
2742           if (isset($values['class'])) {
2743             $checks[$name]["CLASSES_REQUIRED"] = is_array($values['class'])?$values['class']:array($values['class']);
2744           }
2745         }
2746   foreach($checks as $name => $value){
2747     foreach($value['CLASSES_REQUIRED'] as $class){
2749       if(!isset($objectclasses[$name])){
2750         if($value['IS_MUST_HAVE']){
2751           $checks[$name]['STATUS'] = FALSE;
2752           $checks[$name]['MSG']    = sprintf(_("Required object class %s is missing!"), bold($class));
2753         } else {
2754           $checks[$name]['STATUS'] = TRUE;
2755           $checks[$name]['MSG']    = sprintf(_("Optional object class %s is missing!"), bold($class));
2756         }
2757       }elseif(!check_schema_version($objectclasses[$name],$value['REQUIRED_VERSION'])){
2758         $checks[$name]['STATUS'] = FALSE;
2760         $checks[$name]['MSG'] = sprintf(_("Wrong version of required object class %s (!=%s) detected!"), bold($class), bold($value['REQUIRED_VERSION']));
2761       }else{
2762         $checks[$name]['STATUS'] = TRUE;
2763         $checks[$name]['MSG'] = sprintf(_("Class available"));
2764       }
2765     }
2766   }
2768   $tmp = $objectclasses;
2770   /* The gosa base schema */
2771   $checks['posixGroup'] = $def_check;
2772   $checks['posixGroup']['REQUIRED_VERSION'] = "2.6.1";
2773   $checks['posixGroup']['SCHEMA_FILES']     = array("gosa-samba3.schema","gosa-samba2.schema");
2774   $checks['posixGroup']['CLASSES_REQUIRED'] = array("posixGroup");
2775   $checks['posixGroup']['STATUS']           = TRUE;
2776   $checks['posixGroup']['IS_MUST_HAVE']     = TRUE;
2777   $checks['posixGroup']['MSG']              = "";
2778   $checks['posixGroup']['INFO']             = "";
2780   /* Depending on selected rfc2307bis mode, we need different schema configurations */
2781   if(isset($tmp['posixGroup'])){
2783     if($rfc2307bis && isset($tmp['posixGroup']['STRUCTURAL'])){
2784       $checks['posixGroup']['STATUS']           = FALSE;
2785       $checks['posixGroup']['MSG']              = _("RFC 2307bis group schema is enabled, but the current LDAP configuration does not support it!");
2786       $checks['posixGroup']['INFO']             = _("To use RFC 2307bis groups, the objectClass 'posixGroup' must be AUXILIARY.");
2787     }
2788     if(!$rfc2307bis && !isset($tmp['posixGroup']['STRUCTURAL'])){
2789       $checks['posixGroup']['STATUS']           = FALSE;
2790       $checks['posixGroup']['MSG']              = _("RFC 2307bis group schema is disabled, but the current LDAP configuration supports it!");
2791       $checks['posixGroup']['INFO']             = _("To correct this, the objectClass 'posixGroup' must be STRUCTURAL.");
2792     }
2793   }
2795   return($checks);
2799 function get_languages($languages_in_own_language = FALSE,$strip_region_tag = FALSE)
2801   $tmp = array(
2802         "de_DE" => "German",
2803         "fr_FR" => "French",
2804         "it_IT" => "Italian",
2805         "es_ES" => "Spanish",
2806         "en_US" => "English",
2807         "nl_NL" => "Dutch",
2808         "pl_PL" => "Polish",
2809         "pt_BR" => "Brazilian Portuguese",
2810         #"sv_SE" => "Swedish",
2811         "zh_CN" => "Chinese",
2812         "vi_VN" => "Vietnamese",
2813         "ru_RU" => "Russian");
2814   
2815   $tmp2= array(
2816         "de_DE" => _("German"),
2817         "fr_FR" => _("French"),
2818         "it_IT" => _("Italian"),
2819         "es_ES" => _("Spanish"),
2820         "en_US" => _("English"),
2821         "nl_NL" => _("Dutch"),
2822         "pl_PL" => _("Polish"),
2823         "pt_BR" => _("Brazilian Portuguese"),
2824         #"sv_SE" => _("Swedish"),
2825         "zh_CN" => _("Chinese"),
2826         "vi_VN" => _("Vietnamese"),
2827         "ru_RU" => _("Russian"));
2829   $ret = array();
2830   if($languages_in_own_language){
2832     $old_lang = setlocale(LC_ALL, 0);
2834     /* If the locale wasn't correclty set before, there may be an incorrect
2835         locale returned. Something like this: 
2836           C_CTYPE=de_DE.UTF-8;LC_NUMERIC=C;LC_TIME=de_DE.UTF-8;LC ...
2837         Extract the locale name from this string and use it to restore old locale.
2838      */
2839     if(preg_match("/LC_CTYPE/",$old_lang)){
2840       $old_lang = preg_replace("/^.*LC_CTYPE=([^;]*).*$/","\\1",$old_lang);
2841     }
2842     
2843     foreach($tmp as $key => $name){
2844       $lang = $key.".UTF-8";
2845       setlocale(LC_ALL, $lang);
2846       if($strip_region_tag){
2847         $ret[preg_replace("/^([^_]*).*$/","\\1",$key)] = _($name)." (".$tmp2[$key].")";
2848       }else{
2849         $ret[$key] = _($name)." &nbsp;(".$tmp2[$key].")";
2850       }
2851     }
2852     setlocale(LC_ALL, $old_lang);
2853   }else{
2854     foreach($tmp as $key => $name){
2855       if($strip_region_tag){
2856         $ret[preg_replace("/^([^_]*).*/","\\1",$key)] = _($name);
2857       }else{
2858         $ret[$key] = _($name);
2859       }
2860     }
2861   }
2862   return($ret);
2866 /*! \brief Returns contents of the given POST variable and check magic quotes settings
2867  *
2868  * Depending on the magic quotes settings this returns a stripclashed'ed version of
2869  * a certain POST variable.
2870  *
2871  * \param string 'name' the POST var to return ($_POST[$name])
2872  * \return string
2873  * */
2874 function get_post($name)
2876   if(!isset($_POST[$name])){
2877     trigger_error("Requested POST value (".$name.") does not exists, you should add a check to prevent this message.");
2878     return(FALSE);
2879   }
2881   if(get_magic_quotes_gpc()){
2882     return(stripcslashes(validate($_POST[$name])));
2883   }else{
2884     return(validate($_POST[$name]));
2885   }
2889 /*! \brief Return class name in correct case */
2890 function get_correct_class_name($cls)
2892   global $class_mapping;
2893   if(isset($class_mapping) && is_array($class_mapping)){
2894     foreach($class_mapping as $class => $file){
2895       if(preg_match("/^".$cls."$/i",$class)){
2896         return($class);
2897       }
2898     }
2899   }
2900   return(FALSE);
2904 /*! \brief Change the password of a given DN
2905  * 
2906  * Change the password of a given DN with the specified hash.
2907  *
2908  * \param string 'dn' the DN whose password shall be changed
2909  * \param string 'password' the password
2910  * \param int mode
2911  * \param string 'hash' which hash to use to encrypt it, default is empty
2912  * for cleartext storage.
2913  * \return boolean TRUE on success FALSE on error
2914  */
2915 function change_password ($dn, $password, $mode=0, $hash= "")
2917   global $config;
2918   $newpass= "";
2920   /* Convert to lower. Methods are lowercase */
2921   $hash= strtolower($hash);
2923   // Get all available encryption Methods
2925   // NON STATIC CALL :)
2926   $methods = new passwordMethod(session::get('config'),$dn);
2927   $available = $methods->get_available_methods();
2929   // read current password entry for $dn, to detect the encryption Method
2930   $ldap       = $config->get_ldap_link();
2931   $ldap->cat ($dn, array("shadowLastChange", "userPassword", "uid"));
2932   $attrs      = $ldap->fetch ();
2934   /* Is ensure that clear passwords will stay clear */
2935   if($hash == "" && isset($attrs['userPassword'][0]) && !preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0])){
2936     $hash = "clear";
2937   }
2939   // Detect the encryption Method
2940   if ( (isset($attrs['userPassword'][0]) &&  preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0], $matches)) ||  $hash != ""){
2942     /* Check for supported algorithm */
2943     mt_srand((double) microtime()*1000000);
2945     /* Extract used hash */
2946     if ($hash == ""){
2947       $test = passwordMethod::get_method($attrs['userPassword'][0],$dn);
2948     } else {
2949       $test = new $available[$hash]($config,$dn);
2950       $test->set_hash($hash);
2951     }
2953   } else {
2954     // User MD5 by default
2955     $hash= "md5";
2956     $test = new  $available['md5']($config, $dn);
2957   }
2959   if($test instanceOf passwordMethod){
2961     $deactivated = $test->is_locked($config,$dn);
2963     /* Feed password backends with information */
2964     $test->dn= $dn;
2965     $test->attrs= $attrs;
2966     $newpass= $test->generate_hash($password);
2968     // Update shadow timestamp?
2969     if (isset($attrs["shadowLastChange"][0])){
2970       $shadow= (int)(date("U") / 86400);
2971     } else {
2972       $shadow= 0;
2973     }
2975     // Write back modified entry
2976     $ldap->cd($dn);
2977     $attrs= array();
2979     // Not for groups
2980     if ($mode == 0){
2981       // Create SMB Password
2982       $attrs= generate_smb_nt_hash($password);
2984       if ($shadow != 0){
2985         $attrs['shadowLastChange']= $shadow;
2986       }
2987     }
2989     $attrs['userPassword']= array();
2990     $attrs['userPassword']= $newpass;
2992     $ldap->modify($attrs);
2994     /* Read ! if user was deactivated */
2995     if($deactivated){
2996       $test->lock_account($config,$dn);
2997     }
2999     new log("modify","users/passwordMethod",$dn,array_keys($attrs),$ldap->get_error());
3001     if (!$ldap->success()) {
3002       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD, ERROR_DIALOG));
3003     } else {
3005       /* Run backend method for change/create */
3006       if(!$test->set_password($password)){
3007         return(FALSE);
3008       }
3010       /* Find postmodify entries for this class */
3011       $command= $config->get_cfg_value("password","postmodify");
3013       if ($command != ""){
3014         /* Walk through attribute list */
3015         $command= preg_replace("/%userPassword/", $password, $command);
3016         $command= preg_replace("/%dn/", $dn, $command);
3018         if (check_command($command)){
3019           @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Execute");
3020           exec($command);
3021         } else {
3022           $message= sprintf(_("Command %s specified as post modify action for plugin %s does not exist!"), bold($command), bold("password"));
3023           msg_dialog::display(_("Configuration error"), $message, ERROR_DIALOG);
3024         }
3025       }
3026     }
3027     return(TRUE);
3028   }
3032 /*! \brief Generate samba hashes
3033  *
3034  * Given a certain password this constructs an array like
3035  * array['sambaLMPassword'] etc.
3036  *
3037  * \param string 'password'
3038  * \return array contains several keys for lmPassword, ntPassword, pwdLastSet, etc. depending
3039  * on the samba version
3040  */
3041 function generate_smb_nt_hash($password)
3043   global $config;
3045   // First try to retrieve values via RPC 
3046   if ($config->get_cfg_value("core","gosaRpcServer") != ""){
3048     $rpc = $config->getRpcHandle();
3049     $hash = $rpc->mksmbhash($password);
3050     if(!$rpc->success()){
3051         msg_dialog::display(_("Error"),msgPool::rpcError($rpc->get_error()),ERROR_DIALOG);
3052         return("");
3053     }
3055   }elseif ($config->get_cfg_value("core","gosaSupportURI") != ""){
3057     // Try using gosa-si
3058         $res= gosaSupportDaemon::send("gosa_gen_smb_hash", "GOSA", array("password" => $password), TRUE);
3059     if (isset($res['XML']['HASH'])){
3060         $hash= $res['XML']['HASH'];
3061     } else {
3062       $hash= "";
3063     }
3065     if ($hash == "") {
3066       msg_dialog::display(_("Configuration error"), _("Cannot generate SAMBA hash!"), ERROR_DIALOG);
3067       return ("");
3068     }
3069   } else {
3070           $tmp= $config->get_cfg_value("core",'sambaHashHook')." ".escapeshellarg($password);
3071           @DEBUG (DEBUG_LDAP, __LINE__, __FUNCTION__, __FILE__, $tmp, "Execute");
3073           exec($tmp, $ar);
3074           flush();
3075           reset($ar);
3076           $hash= current($ar);
3078     if ($hash == "") {
3079       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);
3080       return ("");
3081     }
3082   }
3084   list($lm,$nt)= explode(":", trim($hash));
3086   $attrs['sambaLMPassword']= $lm;
3087   $attrs['sambaNTPassword']= $nt;
3088   $attrs['sambaPwdLastSet']= date('U');
3089   $attrs['sambaBadPasswordCount']= "0";
3090   $attrs['sambaBadPasswordTime']= "0";
3091   return($attrs);
3095 /*! \brief Get the Change Sequence Number of a certain DN
3096  *
3097  * To verify if a given object has been changed outside of Gosa
3098  * in the meanwhile, this function can be used to get the entryCSN
3099  * from the LDAP directory. It uses the attribute as configured
3100  * in modificationDetectionAttribute
3101  *
3102  * \param string 'dn'
3103  * \return either the result or "" in any other case
3104  */
3105 function getEntryCSN($dn)
3107   global $config;
3108   if(empty($dn) || !is_object($config)){
3109     return("");
3110   }
3112   /* Get attribute that we should use as serial number */
3113   $attr= $config->get_cfg_value("core","modificationDetectionAttribute");
3114   if($attr != ""){
3115     $ldap = $config->get_ldap_link();
3116     $ldap->cat($dn,array($attr));
3117     $csn = $ldap->fetch();
3118     if(isset($csn[$attr][0])){
3119       return($csn[$attr][0]);
3120     }
3121   }
3122   return("");
3126 /*! \brief Add (a) given objectClass(es) to an attrs entry
3127  * 
3128  * The function adds the specified objectClass(es) to the given
3129  * attrs entry.
3130  *
3131  * \param mixed 'classes' Either a single objectClass or several objectClasses
3132  * as an array
3133  * \param array 'attrs' The attrs array to be modified.
3134  *
3135  * */
3136 function add_objectClass($classes, &$attrs)
3138   if (is_array($classes)){
3139     $list= $classes;
3140   } else {
3141     $list= array($classes);
3142   }
3144   foreach ($list as $class){
3145     $attrs['objectClass'][]= $class;
3146   }
3150 /*! \brief Removes a given objectClass from the attrs entry
3151  *
3152  * Similar to add_objectClass, except that it removes the given
3153  * objectClasses. See it for the params.
3154  * */
3155 function remove_objectClass($classes, &$attrs)
3157   if (isset($attrs['objectClass'])){
3158     /* Array? */
3159     if (is_array($classes)){
3160       $list= $classes;
3161     } else {
3162       $list= array($classes);
3163     }
3165     $tmp= array();
3166     foreach ($attrs['objectClass'] as $oc) {
3167       foreach ($list as $class){
3168         if (strtolower($oc) != strtolower($class)){
3169           $tmp[]= $oc;
3170         }
3171       }
3172     }
3173     $attrs['objectClass']= $tmp;
3174   }
3178 /*! \brief  Initialize a file download with given content, name and data type. 
3179  *  \param  string data The content to send.
3180  *  \param  string name The name of the file.
3181  *  \param  string type The content identifier, default value is "application/octet-stream";
3182  */
3183 function send_binary_content($data,$name,$type = "application/octet-stream")
3185   header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
3186   header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
3187   header("Cache-Control: no-cache");
3188   header("Pragma: no-cache");
3189   header("Cache-Control: post-check=0, pre-check=0");
3190   header("Content-type: ".$type."");
3192   $HTTP_USER_AGENT = $_SERVER['HTTP_USER_AGENT'];
3194   /* Strip name if it is a complete path */
3195   if (preg_match ("/\//", $name)) {
3196         $name= basename($name);
3197   }
3198   
3199   /* force download dialog */
3200   if (preg_match('/MSIE 5.5/', $HTTP_USER_AGENT) || preg_match('/MSIE 6.0/', $HTTP_USER_AGENT)) {
3201     header('Content-Disposition: filename="'.$name.'"');
3202   } else {
3203     header('Content-Disposition: attachment; filename="'.$name.'"');
3204   }
3206   echo $data;
3207   exit();
3211 function reverse_html_entities($str,$type = ENT_QUOTES , $charset = "UTF-8")
3213   if(is_string($str)){
3214     return(htmlentities($str,$type,$charset));
3215   }elseif(is_array($str)){
3216     foreach($str as $name => $value){
3217       $str[$name] = reverse_html_entities($value,$type,$charset);
3218     }
3219   }
3220   return($str);
3224 /*! \brief Encode special string characters so we can use the string in \
3225            HTML output, without breaking quotes.
3226     \param string The String we want to encode.
3227     \return string The encoded String
3228  */
3229 function xmlentities($str)
3230
3231   if(is_string($str)){
3233     static $asc2uni= array();
3234     if (!count($asc2uni)){
3235       for($i=128;$i<256;$i++){
3236     #    $asc2uni[chr($i)] = "&#x".dechex($i).";";
3237       }
3238     }
3240     $str = str_replace("&", "&amp;", $str);
3241     $str = str_replace("<", "&lt;", $str);
3242     $str = str_replace(">", "&gt;", $str);
3243     $str = str_replace("'", "&apos;", $str);
3244     $str = str_replace("\"", "&quot;", $str);
3245     $str = str_replace("\r", "", $str);
3246     $str = strtr($str,$asc2uni);
3247     return $str;
3248   }elseif(is_array($str)){
3249     foreach($str as $name => $value){
3250       $str[$name] = xmlentities($value);
3251     }
3252   }
3253   return($str);
3257 /*! \brief  Updates all accessTo attributes from a given value to a new one.
3258             For example if a host is renamed.
3259     \param  String  $from The source accessTo name.
3260     \param  String  $to   The destination accessTo name.
3261 */
3262 function update_accessTo($from,$to)
3264   global $config;
3265   $ldap = $config->get_ldap_link();
3266   $ldap->cd($config->current['BASE']);
3267   $ldap->search("(&(objectClass=trustAccount)(accessTo=".$from."))",array("objectClass","accessTo"));
3268   while($attrs = $ldap->fetch()){
3269     $new_attrs = array("accessTo" => array());
3270     $dn = $attrs['dn'];
3271     for($i = 0 ; $i < $attrs['accessTo']['count']; $i++){
3272       if($attrs['accessTo'][$i] == $from){
3273         if(!empty($to)){
3274           $new_attrs['accessTo'][] =  $to;
3275         }
3276       }else{
3277         $new_attrs['accessTo'][] =  $attrs['accessTo'][$i]; 
3278       }
3279     }
3280     $ldap->cd($dn);
3281     $ldap->modify($new_attrs);
3282     if (!$ldap->success()){
3283       msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD, "update_accessTo($from,$to)"));
3284     }
3285     new log("modify","update_accessTo($from,$to)",$dn,array_keys($new_attrs),$ldap->get_error());
3286   }
3290 /*! \brief Returns a random char */
3291 function get_random_char () {
3292      $randno = rand (0, 63);
3293      if ($randno < 12) {
3294          return (chr ($randno + 46)); // Digits, '/' and '.'
3295      } else if ($randno < 38) {
3296          return (chr ($randno + 53)); // Uppercase
3297      } else {
3298          return (chr ($randno + 59)); // Lowercase
3299      }
3303 function cred_encrypt($input, $password) {
3305   $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
3306   $iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
3308   return bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $password, $input, MCRYPT_MODE_ECB, $iv));
3313 function cred_decrypt($input,$password) {
3314   $size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
3315   $iv = mcrypt_create_iv($size, MCRYPT_DEV_RANDOM);
3317   return mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $password, pack("H*", $input), MCRYPT_MODE_ECB, $iv);
3321 function get_object_info()
3323   return(session::get('objectinfo'));
3327 function set_object_info($str = "")
3329   session::set('objectinfo',$str);
3333 function isIpInNet($ip, $net, $mask) {
3334    // Move to long ints
3335    $ip= ip2long($ip);
3336    $net= ip2long($net);
3337    $mask= ip2long($mask);
3339    // Mask given IP with mask. If it returns "net", we're in...
3340    $res= $ip & $mask;
3342    return ($res == $net);
3346 function get_next_id($attrib, $dn)
3348   global $config;
3350   switch ($config->get_cfg_value("core","idAllocationMethod")){
3351     case "pool":
3352       return get_next_id_pool($attrib);
3353     case "traditional":
3354       return get_next_id_traditional($attrib, $dn);
3355   }
3357   msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("unknown idAllocation method!"), ERROR_DIALOG);
3358   return null;
3362 function get_next_id_pool($attrib) {
3363   global $config;
3365   /* Fill informational values */
3366   $min= $config->get_cfg_value("core","${attrib}PoolMin");
3367   $max= $config->get_cfg_value("core","${attrib}PoolMax");
3369   /* Sanity check */
3370   if ($min >= $max) {
3371     msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." ".sprintf(_("%sPoolMin >= %sPoolMax!"), bold($attrib), bold($attrib)), ERROR_DIALOG);
3372     return null;
3373   }
3375   /* ID to skip */
3376   $ldap= $config->get_ldap_link();
3377   $id= null;
3379   /* Try to allocate the ID several times before failing */
3380   $tries= 3;
3381   while ($tries--) {
3383     /* Look for ID map entry */
3384     $ldap->cd ($config->current['BASE']);
3385     $ldap->search ("(&(objectClass=sambaUnixIdPool)($attrib=*))", array("$attrib"));
3387     /* If it does not exist, create one with these defaults */
3388     if ($ldap->count() == 0) {
3389       /* Fill informational values */
3390       $minUserId= $config->get_cfg_value("core","uidNumberPoolMin");
3391       $minGroupId= $config->get_cfg_value("core","gidNumberPoolMin");
3393       /* Add as default */
3394       $attrs= array("objectClass" => array("organizationalUnit", "sambaUnixIdPool"));
3395       $attrs["ou"]= "idmap";
3396       $attrs["uidNumber"]= $minUserId;
3397       $attrs["gidNumber"]= $minGroupId;
3398       $ldap->cd("ou=idmap,".$config->current['BASE']);
3399       $ldap->add($attrs);
3400       if ($ldap->error != "Success") {
3401         msg_dialog::display(_("Error"), _("Cannot create sambaUnixIdPool entry!"), ERROR_DIALOG);
3402         return null;
3403       }
3404       $tries++;
3405       continue;
3406     }
3407     /* Bail out if it's not unique */
3408     if ($ldap->count() != 1) {
3409       msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("sambaUnixIdPool is not unique!"), ERROR_DIALOG);
3410       return null;
3411     }
3413     /* Store old attrib and generate new */
3414     $attrs= $ldap->fetch();
3415     $dn= $ldap->getDN();
3416     $oldAttr= $attrs[$attrib][0];
3417     $newAttr= $oldAttr + 1;
3419     /* Sanity check */
3420     if ($newAttr >= $max) {
3421       msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("no ID available!"), ERROR_DIALOG);
3422       return null;
3423     }
3424     if ($newAttr < $min) {
3425       msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("no ID available!"), ERROR_DIALOG);
3426       return null;
3427     }
3429     #FIXME: PHP is not able to do a modification of "del: .../add: ...", so this
3430     #       is completely unsafe in the moment.
3431     #/* Remove old attr, add new attr */
3432     #$attrs= array($attrib => $oldAttr);
3433     #$ldap->rm($attrs, $dn);
3434     #if ($ldap->error != "Success") {
3435     #  continue;
3436     #}
3437     $ldap->cd($dn);
3438     $ldap->modify(array($attrib => $newAttr));
3439     if ($ldap->error != "Success") {
3440       msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." ".$ldap->get_error(), ERROR_DIALOG);
3441       return null;
3442     } else {
3443       return $oldAttr;
3444     }
3445   }
3447   /* Bail out if we had problems getting the next id */
3448   if (!$tries) {
3449     msg_dialog::display(_("Error"), _("Cannot allocate free ID:")." "._("maximum number of tries exceeded!"), ERROR_DIALOG);
3450   }
3452   return $id;
3456 function get_next_id_traditional($attrib, $dn)
3458   global $config;
3460   $ids= array();
3461   $ldap= $config->get_ldap_link();
3463   $ldap->cd ($config->current['BASE']);
3464   if (preg_match('/gidNumber/i', $attrib)){
3465     $oc= "posixGroup";
3466   } else {
3467     $oc= "posixAccount";
3468   }
3469   $ldap->search ("(&(objectClass=$oc)($attrib=*))", array("$attrib"));
3471   /* Get list of ids */
3472   while ($attrs= $ldap->fetch()){
3473     $ids[]= (int)$attrs["$attrib"][0];
3474   }
3476   /* Add the nobody id */
3477   $ids[]= 65534;
3479   /* get the ranges */
3480   $tmp = array('0'=> 1000);
3481   if (preg_match('/posixAccount/', $oc) && $config->get_cfg_value("core","uidNumberBase") != ""){
3482     $tmp= explode('-',$config->get_cfg_value("core","uidNumberBase"));
3483   } elseif($config->get_cfg_value("core","gidNumberBase") != ""){
3484     $tmp= explode('-',$config->get_cfg_value("core","gidNumberBase"));
3485   }
3487   /* Set hwm to max if not set - for backward compatibility */
3488   $lwm= $tmp[0];
3489   if (isset($tmp[1])){
3490     $hwm= $tmp[1];
3491   } else {
3492     $hwm= pow(2,32);
3493   }
3494   /* Find out next free id near to UID_BASE */
3495   if ($config->get_cfg_value("core","baseIdHook") == ""){
3496     $base= $lwm;
3497   } else {
3498     /* Call base hook */
3499     $base= get_base_from_hook($dn, $attrib);
3500   }
3501   for ($id= $base; $id++; $id < pow(2,32)){
3502     if (!in_array($id, $ids)){
3503       return ($id);
3504     }
3505   }
3507   /* Should not happen */
3508   if ($id == $hwm){
3509     msg_dialog::display(_("Error"), _("Cannot allocate free ID!"), ERROR_DIALOG);
3510     exit;
3511   }
3515 /* Mark the occurance of a string with a span */
3516 function mark($needle, $haystack, $ignorecase= true)
3518   $result= "";
3520   while (preg_match('/^(.*)('.preg_quote($needle).')(.*)$/i', $haystack, $matches)) {
3521     $result.= $matches[1]."<span class='mark'>".$matches[2]."</span>";
3522     $haystack= $matches[3];
3523   }
3525   return $result.$haystack;
3529 /* Return an image description using the path */
3530 function image($path, $action= "", $title= "", $align= "middle")
3532   global $config;
3533   global $BASE_DIR;
3534   $label= null;
3536   // Bail out, if there's no style file
3537   if(!session::global_is_set("img-styles")){
3539     // Get theme
3540     if (isset ($config)){
3541       $theme= $config->get_cfg_value("core","theme");
3542     } else {
3544       // Fall back to default theme
3545       $theme= "default";
3546     }
3548     if (!file_exists("$BASE_DIR/ihtml/themes/$theme/img.styles")){
3549       die ("No img.style for this theme found!");
3550     }
3552     session::global_set('img-styles', unserialize(file_get_contents("$BASE_DIR/ihtml/themes/$theme/img.styles")));
3553   }
3554   $styles= session::global_get('img-styles');
3556   /* Extract labels from path */
3557   if (preg_match("/\.png\[(.*)\]$/", $path, $matches)) {
3558     $label= $matches[1];
3559   }
3561   $lbl= "";
3562   if ($label) {
3563     if (isset($styles["images/label-".$label.".png"])) {
3564       $lbl= "<div style='".$styles["images/label-".$label.".png"]."'></div>";
3565     } else {
3566       die("Invalid label specified: $label\n");
3567     }
3569     $path= preg_replace("/\[.*\]$/", "", $path);
3570   }
3572   // Non middle layout?
3573   if ($align == "middle") {
3574     $align= "";
3575   } else {
3576     $align= ";vertical-align:$align";
3577   }
3579   // Clickable image or not?
3580   if ($title != "") {
3581     $title= "title='$title'";
3582   }
3583   if ($action == "") {
3584     return "<div class='img' $title style='".$styles[$path]."$align'>$lbl</div>";
3585   } else {
3586     return "<input type='submit' class='img' id='$action' value='' name='$action' $title style='".$styles[$path]."$align'>";
3587   }
3590 /*! \brief    Encodes a complex string to be useable in HTML posts.
3591  */
3592 function postEncode($str)
3594   return(preg_replace("/=/","_", base64_encode($str)));
3597 /*! \brief    Decodes a string encoded by postEncode
3598  */
3599 function postDecode($str)
3601   return(base64_decode(preg_replace("/_/","=", $str)));
3605 /*! \brief    Generate styled output
3606  */
3607 function bold($str)
3609   return "<span class='highlight'>$str</span>";
3613 // vim:tabstop=2:expandtab:shiftwidth=2:filetype=php:syntax:ruler:
3614 ?>