Code

Fixed proposal pre-selection
[gosa.git] / gosa-core / include / functions.inc
index 33ef64f6d9e74e17a808805f4d159b4a5cbaa706..8307b6e63ee5c6996ef2a3821aad4484ccd1dec5 100644 (file)
@@ -2060,6 +2060,13 @@ function gen_uids($rule, $attributes)
 {
   global $config;
 
+  // Strip out non ascii chars
+  foreach($attributes as $name => $value){
+      $value = iconv('UTF-8', 'US-ASCII//TRANSLIT', $value);
+      $value = preg_replace('/[^(\x20-\x7F)]*/','',$value);
+      $attributes[$name] = $value;
+  }
+
   /* Search for keys and fill the variables array with all 
      possible values for that key. */
   $part= "";
@@ -2339,11 +2346,7 @@ function clean_smarty_compile_dir($directory)
               msg_dialog::display(_("Internal error"), sprintf(_("File %s cannot be deleted!"), bold($directory."/".$file)), ERROR_DIALOG);
               // This should never be reached
             }
-          } elseif(is_dir($directory."/".$file) &&
-              is_writable($directory."/".$file)) {
-            // Just recursively delete it
-            rmdirRecursive($directory."/".$file);
-          }
+          } 
         }
         // We should now create a fresh revision file
         clean_smarty_compile_dir($directory);
@@ -2981,138 +2984,213 @@ function get_correct_class_name($cls)
 }
 
 
-/*! \brief Change the password of a given DN
- * 
- * Change the password of a given DN with the specified hash.
- *
- * \param string 'dn' the DN whose password shall be changed
- * \param string 'password' the password
- * \param int mode
- * \param string 'hash' which hash to use to encrypt it, default is empty
- * for cleartext storage.
- * \return boolean TRUE on success FALSE on error
+/*! \brief  Change the password for a given object ($dn).
+ *          This method uses the specified hashing method to generate a new password
+ *           for the object and it also takes care of sambaHashes, if enabled.
+ *          Finally the postmodify hook of the class 'user' will be called, if it is set.
+ *
+ * @param   String   The DN whose password shall be changed.
+ * @param   String   The new password.
+ * @param   Boolean  Skip adding samba hashes to the target (sambaNTPassword,sambaLMPassword)
+ * @param   String   The hashin method to use, default is the global configured default.
+ * @param   String   The users old password, this allows script based rollback mechanisms,
+ *                    the prehook will then be called witch switched newPassword/oldPassword. 
+ * @return  Boolean  TRUE on success else FALSE.
  */
-function change_password ($dn, $password, $mode=0, $hash= "")
+function change_password ($dn, $password, $mode=FALSE, $hash= "", $old_password = "", &$message = "")
 {
-  global $config;
-  $newpass= "";
-
-  /* Convert to lower. Methods are lowercase */
-  $hash= strtolower($hash);
-
-  // Get all available encryption Methods
-
-  // NON STATIC CALL :)
-  $methods = new passwordMethod(session::get('config'),$dn);
-  $available = $methods->get_available_methods();
-
-  // read current password entry for $dn, to detect the encryption Method
-  $ldap       = $config->get_ldap_link();
-  $ldap->cat ($dn, array("shadowLastChange", "userPassword", "uid"));
-  $attrs      = $ldap->fetch ();
-
-  /* Is ensure that clear passwords will stay clear */
-  if($hash == "" && isset($attrs['userPassword'][0]) && !preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0])){
-    $hash = "clear";
-  }
-
-  // Detect the encryption Method
-  if ( (isset($attrs['userPassword'][0]) &&  preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0], $matches)) ||  $hash != ""){
+    global $config;
+    $newpass= "";
 
-    /* Check for supported algorithm */
+    // Not sure, why this is here, but maybe some encryption methods require it.
     mt_srand((double) microtime()*1000000);
 
-    /* Extract used hash */
-    if ($hash == ""){
-      $test = passwordMethod::get_method($attrs['userPassword'][0],$dn);
-    } else {
-      $test = new $available[$hash]($config,$dn);
-      $test->set_hash($hash);
-    }
-
-  } else {
-    // User MD5 by default
-    $hash= "md5";
-    $test = new  $available['md5']($config, $dn);
-  }
-
-  if($test instanceOf passwordMethod){
+    // Get a list of all available password encryption methods.
+    $methods = new passwordMethod(session::get('config'),$dn);
+    $available = $methods->get_available_methods();
 
-    stats::log('global', 'global', array('users'),  $action = 'change_password', $amount = 1, 0, $test->get_hash());
-
-    $deactivated = $test->is_locked($config,$dn);
+    // Fetch the current object data, to be able to detect the current hashing method
+    //  and to be able to rollback changes once has an error occured.
+    $ldap = $config->get_ldap_link();
+    $ldap->cat ($dn, array("shadowLastChange", "userPassword","sambaNTPassword","sambaLMPassword", "uid"));
+    $attrs = $ldap->fetch ();
+    $initialAttrs = $attrs;
+
+    // If no hashing method is enforced, then detect what method we've to use.
+    $hash = strtolower($hash);
+    if(empty($hash)){
+
+        // Do we need clear-text password for this object?
+        if(isset($attrs['userPassword'][0]) && !preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0])){
+            $hash = "clear";
+            $test = new $available[$hash]($config,$dn);
+            $test->set_hash($hash);
+        }
 
-    /* Feed password backends with information */
-    $test->dn= $dn;
-    $test->attrs= $attrs;
-    $newpass= $test->generate_hash($password);
+        // If we've still no valid hashing method detected, then try to extract if from the userPassword attribute.
+        elseif(isset($attrs['userPassword'][0]) && preg_match ("/^{([^}]+)}(.+)/", $attrs['userPassword'][0], $matches)){
+            $test = passwordMethod::get_method($attrs['userPassword'][0],$dn);
+            $hash = $test->get_hash_name();
+        }
 
-    // Update shadow timestamp?
-    if (isset($attrs["shadowLastChange"][0])){
-      $shadow= (int)(date("U") / 86400);
-    } else {
-      $shadow= 0;
+        // No current password was found and no hash is enforced, so we've to use the config default here.
+        $hash = $config->get_cfg_value('core','passwordDefaultHash');
+        $test = new $available[$hash]($config,$dn);
+        $test->set_hash($hash);
+    }else{
+        $test = new $available[$hash]($config,$dn);
+        $test->set_hash($hash);
     }
 
-    // Write back modified entry
-    $ldap->cd($dn);
-    $attrs= array();
+    // We've now a valid password-method-handle and can create the new password hash or don't we?
+    if(!$test instanceOf passwordMethod){
+        $message = _("Cannot detect password hash!");
+    }else{
 
-    // Not for groups
-    if ($mode == 0){
+        // Feed password backends with object information. 
+        $test->dn = $dn;
+        $test->attrs = $attrs;
+        $newpass= $test->generate_hash($password);
 
+        // Do we have to append samba attributes too?
+        // - sambaNTPassword / sambaLMPassword
         $tmp = $config->get_cfg_value('core','sambaHashHook');
-        if(!empty($tmp)){
-
-            // Create SMB Password
+        $attrs= array();
+        if (!$mode && !empty($tmp)){
             $attrs= generate_smb_nt_hash($password);
-
+            $shadow = (isset($attrs["shadowLastChange"][0]))?(int)(date("U") / 86400):0;
             if ($shadow != 0){
                 $attrs['shadowLastChange']= $shadow;
             }
         }
-    }
-
-    $attrs['userPassword']= array();
-    $attrs['userPassword']= $newpass;
 
-    $ldap->modify($attrs);
+        // Write back the new password hash 
+        $ldap->cd($dn);
+        $attrs['userPassword']= $newpass;
 
-    /* Read ! if user was deactivated */
-    if($deactivated){
-      $test->lock_account($config,$dn);
-    }
+        // Prepare a special attribute list, which will be used for event hook calls
+        $attrsEvent = array();
+        foreach($initialAttrs as $name => $value){
+            if(!is_numeric($name))
+                $attrsEvent[$name] = escapeshellarg($value[0]);
+        }
+        $attrsEvent['dn'] = escapeshellarg($initialAttrs['dn']);
+        foreach($attrs as $name => $value){
+            $attrsEvent[$name] = escapeshellarg($value);
+        }
+        $attrsEvent['current_password'] = escapeshellarg($old_password);
+        $attrsEvent['new_password'] = escapeshellarg($password);
+
+        // Call the premodify hook now
+        $passwordPlugin = new password($config,$dn);
+        plugin::callHook($passwordPlugin, 'PREMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
+        if($retCode === 0 && count($output)){
+            $message = sprintf(_("Pre-event hook reported a problem: %s. Password change canceled!"),implode($output));
+            return(FALSE);
+        }
 
-    new log("modify","users/passwordMethod",$dn,array_keys($attrs),$ldap->get_error());
+        // Perform ldap operations
+        $ldap->modify($attrs);
 
-    if (!$ldap->success()) {
-      msg_dialog::display(_("LDAP error"), msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD, ERROR_DIALOG));
-    } else {
+        // Check if the object was locked before, if it was, lock it again!
+        $deactivated = $test->is_locked($config,$dn);
+        if($deactivated){
+            $test->lock_account($config,$dn);
+        }
 
-      /* Run backend method for change/create */
-      if(!$test->set_password($password)){
-        return(FALSE);
-      }
+        // Check if everything went fine and then call the post event hooks.
+        // If an error occures, then try to rollback the complete actions done.
+        $preRollback = FALSE;
+        $ldapRollback = FALSE;
+        $success = TRUE;
+        if (!$ldap->success()) {
+            new log("modify","users/passwordMethod",$dn,array(),"Password change - ldap modifications! - FAILED");
+            $success =FALSE;
+            $message = msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD);
+            $preRollback  =TRUE;
+        } else {
 
-      /* Find postmodify entries for this class */
-      $command= $config->get_cfg_value("password","postmodify");
+            // Now call the passwordMethod change mechanism.
+            if(!$test->set_password($password)){
+                $ldapRollback = TRUE;
+                $preRollback  =TRUE;
+                $success = FALSE;
+                new log("modify","users/passwordMethod",$dn,array(),"Password change - set_password! - FAILED");
+                $message = _("Password change failed!");
+            }else{
+        
+                // Execute the password hook
+                plugin::callHook($passwordPlugin, 'POSTMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
+                if($retCode === 0){
+                    if(count($output)){
+                        new log("modify","users/passwordMethod",$dn,array(),"Password change - Post modify hook reported! - FAILED!");
+                        $message = sprintf(_("Post-event hook reported a problem: %s. Password change canceled!"),implode($output));
+                        $ldapRollback = TRUE;
+                        $preRollback = TRUE;
+                        $success = FALSE;
+                    }else{
+                        #new log("modify","users/passwordMethod",$dn,array(),"Password change - successfull!");
+                    }
+                }else{
+                    $ldapRollback = TRUE;
+                    $preRollback = TRUE;
+                    $success = FALSE;
+                    new log("modify","users/passwordMethod",$dn,array(),"Password change - postmodify hook execution! - FAILED");
+                    new log("modify","users/passwordMethod",$dn,array(),$error);
+
+                    // Call password method again and send in old password to 
+                    //  keep the database consistency
+                    $test->set_password($old_password);
+                }
+            }
+        }
 
-      if ($command != ""){
-        /* Walk through attribute list */
-        $command= preg_replace("/%userPassword/", escapeshellarg($password), $command);
-        $command= preg_replace("/%dn/", escapeshellarg($dn), $command);
+        // Setting the password in the ldap database or further operation failed, we should now execute 
+        //  the plugins pre-event hook, using switched passwords, new/old password.
+        // This ensures that passwords which were set outside of GOsa, will be reset to its 
+        //  starting value.
+        if($preRollback){
+            new log("modify","users/passwordMethod",$dn,array(),"Rolling back premodify hook!");
+            $oldpass= $test->generate_hash($old_password);
+            $attrsEvent['current_password'] = escapeshellarg($password);
+            $attrsEvent['new_password'] = escapeshellarg($old_password);
+            foreach(array("userPassword","sambaNTPassword","sambaLMPassword") as $attr){
+                if(isset($initialAttrs[$attr][0])) $attrsEvent[$attr] = $initialAttrs[$attr][0];
+            }
+            
+            plugin::callHook($passwordPlugin, 'PREMODIFY', $attrsEvent, $output,$retCode,$error, $directlyPrintError = FALSE);
+            if($retCode === 0 && count($output)){
+                $message = sprintf(_("Pre-event hook reported a problem: %s. Password change canceled!"),implode($output));
+                new log("modify","users/passwordMethod",$dn,array(),"Rolling back premodify hook! - FAILED!");
+            }
+        }
+        
+        // We've written the password to the ldap database, but executing the postmodify hook failed.
+        // Now, we've to rollback all password related ldap operations.
+        if($ldapRollback){
+            new log("modify","users/passwordMethod",$dn,array(),"Rolling back ldap modifications!");
+            $attrs = array();
+            foreach(array("userPassword","sambaNTPassword","sambaLMPassword") as $attr){
+                if(isset($initialAttrs[$attr][0])) $attrs[$attr] = $initialAttrs[$attr][0];
+            }
+            $ldap->cd($dn);
+            $ldap->modify($attrs);
+            if(!$ldap->success()){
+                $message = msgPool::ldaperror($ldap->get_error(), $dn, LDAP_MOD);
+                new log("modify","users/passwordMethod",$dn,array(),"Rolling back ldap modifications! - FAILED");
+            }
+        }
 
-        if (check_command($command)){
-          @DEBUG (DEBUG_SHELL, __LINE__, __FUNCTION__, __FILE__, $command, "Execute");
-          exec($command);
-        } else {
-          $message= sprintf(_("Command %s specified as post modify action for plugin %s does not exist!"), bold($command), bold("password"));
-          msg_dialog::display(_("Configuration error"), $message, ERROR_DIALOG);
+        // Log action.
+        if($success){
+            stats::log('global', 'global', array('users'),  $action = 'change_password', $amount = 1, 0, $test->get_hash());
+            new log("modify","users/passwordMethod",$dn,array(),"Password change - successfull!");
+        }else{
+            new log("modify","users/passwordMethod",$dn,array(),"Password change - FAILED!");
         }
-      }
+
+        return($success);
     }
-    return(TRUE);
-  }
 }