diff --git a/install/lib/installer_base.lib.php b/install/lib/installer_base.lib.php
index ed3c04330a24f475b8a85c59c7bdbe7e3b821ec4..d68a20842a64c8eb6d3843268bb5722d4e890633 100644
--- a/install/lib/installer_base.lib.php
+++ b/install/lib/installer_base.lib.php
@@ -233,33 +233,10 @@ class installer_base {
 			$sql = "INSERT INTO `server` (`server_id`, `sys_userid`, `sys_groupid`, `sys_perm_user`, `sys_perm_group`, `sys_perm_other`, `server_name`, `mail_server`, `web_server`, `dns_server`, `file_server`, `db_server`, `vserver_server`, `config`, `updated`, `active`) VALUES ('".$conf['server_id']."',1, 1, 'riud', 'riud', 'r', '".$conf['hostname']."', '$mail_server_enabled', '$web_server_enabled', '$dns_server_enabled', '$file_server_enabled', '$db_server_enabled', '$vserver_server_enabled', '$server_ini_content', 0, 1);";
 			$this->db->query($sql);
 			
-			//* insert the ispconfig user in the remote server
-			$from_host = $conf['hostname'];
-			$from_ip = gethostbyname($conf['hostname']);
-			
 			//* username for the ispconfig user
 			$conf['mysql']['master_ispconfig_user'] = 'ispcsrv'.$conf['server_id'];
-		
-			//* Delete ISPConfig user in the master database, in case that it exists
-			$this->dbmaster->query("DELETE FROM mysql.user WHERE User = '".$conf['mysql']['master_ispconfig_user']."' AND Host = '".$from_host."';");
-			$this->dbmaster->query("DELETE FROM mysql.db WHERE Db = '".$conf['mysql']['master_database']."' AND Host = '".$from_host."';");
-			$this->dbmaster->query("DELETE FROM mysql.user WHERE User = '".$conf['mysql']['master_ispconfig_user']."' AND Host = '".$from_ip."';");
-			$this->dbmaster->query("DELETE FROM mysql.db WHERE Db = '".$conf['mysql']['master_database']."' AND Host = '".$from_ip."';");
-			$this->dbmaster->query('FLUSH PRIVILEGES;');
-		
-			//* Create the ISPConfig database user in the remote database
-        	$query = 'GRANT SELECT, INSERT, UPDATE, DELETE ON '.$conf['mysql']['master_database'].".* "
-                	."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$from_host."' "
-                	."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
-			if(!$this->dbmaster->query($query)) {
-				$this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
-			}
-			$query = 'GRANT SELECT, INSERT, UPDATE, DELETE ON '.$conf['mysql']['master_database'].".* "
-                	."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$from_ip."' "
-                	."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
-			if(!$this->dbmaster->query($query)) {
-				$this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
-			}
+            
+            $this->grant_master_database_rights();
 		
 		} else {
 			//* Insert the server, if its not a mster / slave setup
@@ -272,6 +249,78 @@ class installer_base {
 		
 	}
 	
+    public function grant_master_database_rights()
+    {
+        global $conf;
+        
+        if($conf['mysql']['master_slave_setup'] != 'y') return;
+        
+        //* insert the ispconfig user in the remote server
+        $from_host = $conf['hostname'];
+        $from_ip = gethostbyname($conf['hostname']);
+        
+        //* Delete ISPConfig user in the master database, in case that it exists
+        $this->dbmaster->query("DELETE FROM mysql.user WHERE User = '".$conf['mysql']['master_ispconfig_user']."' AND Host = '".$from_host."';");
+        $this->dbmaster->query("DELETE FROM mysql.db WHERE Db = '".$conf['mysql']['master_database']."' AND Host = '".$from_host."';");
+        $this->dbmaster->query("DELETE FROM mysql.user WHERE User = '".$conf['mysql']['master_ispconfig_user']."' AND Host = '".$from_ip."';");
+        $this->dbmaster->query("DELETE FROM mysql.db WHERE Db = '".$conf['mysql']['master_database']."' AND Host = '".$from_ip."';");
+        $this->dbmaster->query('FLUSH PRIVILEGES;');
+    
+        $hosts = array($from_host, $from_ip);
+        
+        foreach($hosts as $src_host) {
+            //* Create the ISPConfig database user in the remote database
+            $query = "GRANT SELECT ON ".$conf['mysql']['master_database'].".`server` "
+                    ."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$src_host."' "
+                    ."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
+            if(!$this->dbmaster->query($query)) {
+                $this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
+            }
+            
+            $query = "GRANT SELECT, INSERT ON ".$conf['mysql']['master_database'].".`sys_log` "
+                    ."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$src_host."' "
+                    ."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
+            if(!$this->dbmaster->query($query)) {
+                $this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
+            }
+            
+            $query = "GRANT SELECT, UPDATE(`status`) ON ".$conf['mysql']['master_database'].".`sys_datalog` "
+                    ."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$src_host."' "
+                    ."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
+            if(!$this->dbmaster->query($query)) {
+                $this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
+            }
+            
+            $query = "GRANT UPDATE(`status`) ON ".$conf['mysql']['master_database'].".`software_update_inst` "
+                    ."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$src_host."' "
+                    ."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
+            if(!$this->dbmaster->query($query)) {
+                $this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
+            }
+            
+            $query = "GRANT UPDATE (`ssl_request`, `ssl_cert`, `ssl_action`) ON ".$conf['mysql']['master_database'].".`web_domain` "
+                    ."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$src_host."' "
+                    ."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
+            if(!$this->dbmaster->query($query)) {
+                $this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
+            }
+            
+            $query = "GRANT SELECT ON ".$conf['mysql']['master_database'].".`sys_group` "
+                    ."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$src_host."' "
+                    ."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
+            if(!$this->dbmaster->query($query)) {
+                $this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
+            }
+            
+            $query = "GRANT INSERT , DELETE ON ".$conf['mysql']['master_database'].".`monitor_data` "
+                    ."TO '".$conf['mysql']['master_ispconfig_user']."'@'".$src_host."' "
+                    ."IDENTIFIED BY '".$conf['mysql']['master_ispconfig_password']."';";
+            if(!$this->dbmaster->query($query)) {
+                $this->error('Unable to create database user in master database: '.$conf['mysql']['master_ispconfig_user'].' Error: '.$this->dbmaster->errorMessage);
+            }
+        }
+    
+    }
 
     //** writes postfix configuration files
     public function process_postfix_config($configfile)
diff --git a/install/sql/ispconfig3.sql b/install/sql/ispconfig3.sql
index e6f9dc8970e2cac4d528d8d544baaf3eb72d17f9..448c0a846a9846efcf1e07276771540e334308ee 100644
--- a/install/sql/ispconfig3.sql
+++ b/install/sql/ispconfig3.sql
@@ -1010,6 +1010,7 @@ CREATE TABLE `web_database` (
   `database_password` varchar(64) default NULL,
   `database_charset` varchar(64) default NULL,
   `remote_access` enum('n','y') NOT NULL default 'y',
+  `remote_ips` text NOT NULL,
   `active` enum('n','y') NOT NULL default 'y',
   PRIMARY KEY  (`database_id`)
 ) ENGINE=MyISAM AUTO_INCREMENT=1;
diff --git a/install/update.php b/install/update.php
index d4d0058e5e3f6d7655987d58fdb8c020f7493295..727dfd0e53c7672bdbf830a53157e2a07d5c3683 100644
--- a/install/update.php
+++ b/install/update.php
@@ -165,6 +165,9 @@ if( !$inst->db->query('DROP DATABASE IF EXISTS '.$conf['mysql']['database']) ) {
 //** Create the mysql database
 $inst->configure_database();
 
+//** Update master database rights
+$inst->grant_master_database_rights();
+
 //** empty all databases
 $db_tables = $inst->db->getTables();
 
diff --git a/interface/lib/classes/validate_database.inc.php b/interface/lib/classes/validate_database.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..c4ea5d027801f0db3811e00b7423adc5fb18b42b
--- /dev/null
+++ b/interface/lib/classes/validate_database.inc.php
@@ -0,0 +1,72 @@
+<?php
+
+/*
+Copyright (c) 2007, Till Brehm, projektfarm Gmbh
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+class validate_database {
+	
+	/*
+		Validator function to check if a given list of ips is ok.
+	*/
+	function valid_ip_list($field_name, $field_value, $validator) {
+		global $app;
+		
+    if($_POST["remote_access"] == "y") {
+        if(trim($field_value) == "") return;
+        
+        $values = split(",", $field_value);
+        foreach($values as $cur_value) {
+            $cur_value = trim($cur_value);
+            
+            $valid = true;
+            if(preg_match("/^[0-9]{1,3}(\.)[0-9]{1,3}(\.)[0-9]{1,3}(\.)[0-9]{1,3}$/", $cur_value)) {
+                $groups = explode(".", $cur_value);
+                foreach($groups as $group){
+                  if($group<0 OR $group>255)
+                  $valid=false;
+                }
+            } else {
+                $valid = false;
+            }
+            
+            if($valid == false) {
+                $errmsg = $validator['errmsg'];
+                if(isset($app->tform->wordbook[$errmsg])) {
+                    return $app->tform->wordbook[$errmsg]."<br>\r\n";
+                } else {
+                    return $errmsg."<br>\r\n";
+                }
+            }
+        }
+    }
+  }
+	
+	
+	
+	
+}
\ No newline at end of file
diff --git a/interface/lib/classes/validate_ftpuser.inc.php b/interface/lib/classes/validate_ftpuser.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..7e04cb99ca367f1a7e40270b42cfa1467e453cb2
--- /dev/null
+++ b/interface/lib/classes/validate_ftpuser.inc.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+Copyright (c) 2007, Till Brehm, projektfarm Gmbh
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+class validate_ftpuser {
+	
+	/*
+		Validator function to check if a given dir is ok.
+	*/
+	function ftp_dir($field_name, $field_value, $validator) {
+		global $app;
+		
+        if($app->tform->primary_id == 0) {
+            $errmsg = $validator['errmsg'];
+            if(isset($app->tform->wordbook[$errmsg])) {
+                return $app->tform->wordbook[$errmsg]."<br>\r\n";
+            } else {
+                return $errmsg."<br>\r\n";
+            }
+        }
+        
+        
+        $ftp_data = $app->db->queryOneRecord("SELECT parent_domain_id FROM ftp_user WHERE ftp_user_id = '".$app->db->quote($app->tform->primary_id)."'");
+        if(!$ftp_data["parent_domain_id"]) {
+            $errmsg = $validator['errmsg'];
+            if(isset($app->tform->wordbook[$errmsg])) {
+                return $app->tform->wordbook[$errmsg]."<br>\r\n";
+            } else {
+                return $errmsg."<br>\r\n";
+            }
+        }
+        
+        $domain_data = $app->db->queryOneRecord("SELECT domain_id, document_root FROM web_domain WHERE domain_id = '".$app->db->quote($ftp_data["parent_domain_id"])."'");
+        if(!$domain_data["domain_id"]) {
+            $errmsg = $validator['errmsg'];
+            if(isset($app->tform->wordbook[$errmsg])) {
+                return $app->tform->wordbook[$errmsg]."<br>\r\n";
+            } else {
+                return $errmsg."<br>\r\n";
+            }
+        }
+        
+        $doc_root = $domain_data["document_root"];
+        $is_ok = false;
+        if($doc_root == $field_value) $is_ok = true;
+        
+        $doc_root .= "/";
+        if(substr($field_value, 0, strlen($doc_root)) == $doc_root) $is_ok = true;
+        
+        if($is_ok == false) {
+            $errmsg = $validator['errmsg'];
+            if(isset($app->tform->wordbook[$errmsg])) {
+                return $app->tform->wordbook[$errmsg]."<br>\r\n";
+            } else {
+                return $errmsg."<br>\r\n";
+            }
+        }
+	}
+	
+	
+	
+	
+}
\ No newline at end of file
diff --git a/interface/web/admin/software_package_list.php b/interface/web/admin/software_package_list.php
index 19a7518a12fc177b4c769eb3e55e7bfb10243810..24c53ec455cc3ec08d4e3b469e13e752315bd326 100644
--- a/interface/web/admin/software_package_list.php
+++ b/interface/web/admin/software_package_list.php
@@ -62,7 +62,46 @@ if(is_array($repos)) {
 				}
 			}
 		}
-	
+        
+        $packages = $app->db->queryAllRecords("SELECT software_package.package_name, v1, v2, v3, v4 FROM software_package LEFT JOIN software_update ON ( software_package.package_name = software_update.package_name ) GROUP BY package_name ORDER BY v1 DESC , v2 DESC , v3 DESC , v4 DESC");
+        if(is_array($packages)) {
+            foreach($packages as $p) {
+            
+                $version = $p['v1'].'.'.$p['v2'].'.'.$p['v3'].'.'.$p['v4'];
+                $updates = $client->get_updates($p['package_name'], $version,$repo['repo_username'], $repo['repo_password']);
+                
+                if(is_array($updates)) {
+                    foreach($updates as $u) {
+                        
+                        $version_array = explode('.',$u['version']);
+                        $v1 = intval($version_array[0]);
+                        $v2 = intval($version_array[1]);
+                        $v3 = intval($version_array[2]);
+                        $v4 = intval($version_array[3]);
+                        
+                        $package_name = $app->db->quote($u['package_name']);
+                        $software_repo_id = intval($repo['software_repo_id']);
+                        $update_url = $app->db->quote($u['url']);
+                        $update_md5 = $app->db->quote($u['md5']);
+                        $update_dependencies = (isset($u['dependencies']))?$app->db->quote($u['dependencies']):'';
+                        $update_title = $app->db->quote($u['title']);
+                        $type = $app->db->quote($u['type']);
+                        
+                        // Check that we do not have this update in the database yet
+                        $sql = "SELECT * FROM software_update WHERE package_name = '$package_name' and v1 = '$v1' and v2 = '$v2' and v3 = '$v3' and v4 = '$v4'";
+                        $tmp = $app->db->queryOneRecord($sql);
+                        if(!isset($tmp['software_update_id'])) {
+                            // Insert the update in the datbase
+                            $sql = "INSERT INTO software_update (software_repo_id, package_name, update_url, update_md5, update_dependencies, update_title, v1, v2, v3, v4, type) 
+                            VALUES ($software_repo_id, '$package_name', '$update_url', '$update_md5', '$update_dependencies', '$update_title', '$v1', '$v2', '$v3', '$v4', '$type')";
+                            //die($sql);
+                            $app->db->query($sql);
+                        }
+                        
+                    }
+                }
+            }
+        }
 	}
 }
 
@@ -74,8 +113,8 @@ if(isset($_GET['action']) && $_GET['action'] == 'install' && $_GET['package'] !=
 	$tmp = $app->db->queryOneRecord($sql);
 	$software_update_id = $tmp['software_update_id'];
 	
-	// $insert_data = "(package_name, server_id, software_update_id, status) VALUES ('$package_name', '$server_id', '$software_update_id','installing')";
-	$insert_data = "(package_name, server_id, software_update_id, status) VALUES ('$package_name', '$server_id', '$software_update_id','installed')";
+	$insert_data = "(package_name, server_id, software_update_id, status) VALUES ('$package_name', '$server_id', '$software_update_id','installing')";
+	// $insert_data = "(package_name, server_id, software_update_id, status) VALUES ('$package_name', '$server_id', '$software_update_id','installed')";
 	$app->db->datalogInsert('software_update_inst', $insert_data, 'software_update_inst_id');
 	
 }
@@ -100,8 +139,10 @@ if(is_array($packages)) {
 			
 			if($inst['status'] == 'installed') {
 				$installed_txt .= $s['server_name'].": Installed version $version<br />";
-			} elseif ($inst['status'] == 'installing') {
-				$installed_txt .= $s['server_name'].": Installation in progress<br />";
+            } elseif ($inst['status'] == 'installing') {
+                $installed_txt .= $s['server_name'].": Installation in progress<br />";
+            } elseif ($inst['status'] == 'failed') {
+                $installed_txt .= $s['server_name'].": Installation failed<br />";
 			} elseif ($inst['status'] == 'deleting') {
 				$installed_txt .= $s['server_name'].": Deletion in progress<br />";
 			} else {
diff --git a/interface/web/admin/software_update_list.php b/interface/web/admin/software_update_list.php
index d4255b76c6279582182355ba0d61dedd2db3dfc6..03d2658667c4b00856ec4f6aa1b176baec28a7c0 100644
--- a/interface/web/admin/software_update_list.php
+++ b/interface/web/admin/software_update_list.php
@@ -104,8 +104,8 @@ if(isset($_GET['action']) && $_GET['action'] == 'install' && $_GET['package'] !=
 	$server_id = intval($_GET['server_id']);
 	$software_update_id = intval($_GET['id']);
 	
-	// $insert_data = "(package_name, server_id, software_update_id, status) VALUES ('$package_name', '$server_id', '$software_update_id','installing')";
-	$insert_data = "(package_name, server_id, software_update_id, status) VALUES ('$package_name', '$server_id', '$software_update_id','installed')";
+	$insert_data = "(package_name, server_id, software_update_id, status) VALUES ('$package_name', '$server_id', '$software_update_id','installing')";
+	// $insert_data = "(package_name, server_id, software_update_id, status) VALUES ('$package_name', '$server_id', '$software_update_id','installed')";
 	$app->db->datalogInsert('software_update_inst', $insert_data, 'software_update_inst_id');
 	
 }
diff --git a/interface/web/sites/form/database.tform.php b/interface/web/sites/form/database.tform.php
index 7ea10eed6f6b9da83495072a2be92029b975dc6d..3a7a947daa4294241830a71fc65ff2fac92cb760 100644
--- a/interface/web/sites/form/database.tform.php
+++ b/interface/web/sites/form/database.tform.php
@@ -133,6 +133,18 @@ $form["tabs"]['database'] = array (
 			'default'	=> 'y',
 			'value'		=> array(0 => 'n',1 => 'y')
 		),
+    'remote_ips' => array (
+      'datatype'  => 'TEXT',
+      'formtype'  => 'TEXT',
+      'validators'  => array (  0 => array (  'type' => 'CUSTOM',
+                                              'class' => 'validate_database',
+                                              'function' => 'valid_ip_list',
+                                              'errmsg' => 'database_remote_error_ips'),
+                             ),
+      'default' => '',
+      'value'   => '',
+      'width'   => '60'
+    ),
 	##################################
 	# ENDE Datatable fields
 	##################################
diff --git a/interface/web/sites/form/ftp_user.tform.php b/interface/web/sites/form/ftp_user.tform.php
index 4950a1b2d649abc0206c2261aacb67661a5333d9..a1d6e156fefb2b26ad692998210a3551e9b73664 100644
--- a/interface/web/sites/form/ftp_user.tform.php
+++ b/interface/web/sites/form/ftp_user.tform.php
@@ -131,93 +131,125 @@ $form["tabs"]['ftp'] = array (
 if($_SESSION["s"]["user"]["typ"] == 'admin') {
 
 $form["tabs"]['advanced'] = array (
-	'title' 	=> "Options",
-	'width' 	=> 100,
-	'template' 	=> "templates/ftp_user_advanced.htm",
-	'fields' 	=> array (
-	##################################
-	# Begin Datatable fields
-	##################################
-		'uid' => array (
-			'datatype'	=> 'VARCHAR',
-			'formtype'	=> 'TEXT',
-			'validators'	=> array ( 	0 => array (	'type'	=> 'NOTEMPTY',
-														'errmsg'=> 'uid_error_empty'),
-									),
-			'default'	=> '0',
-			'value'		=> '',
-			'width'		=> '30',
-			'maxlength'	=> '255'
-		),
-		'gid' => array (
-			'datatype'	=> 'VARCHAR',
-			'formtype'	=> 'TEXT',
-			'validators'	=> array ( 	0 => array (	'type'	=> 'NOTEMPTY',
-														'errmsg'=> 'uid_error_empty'),
-									),
-			'default'	=> '0',
-			'value'		=> '',
-			'width'		=> '30',
-			'maxlength'	=> '255'
-		),
-		'dir' => array (
-			'datatype'	=> 'VARCHAR',
-			'formtype'	=> 'TEXT',
-			'validators'	=> array ( 	0 => array (	'type'	=> 'NOTEMPTY',
-														'errmsg'=> 'directory_error_empty'),
-									),
-			'default'	=> '',
-			'value'		=> '',
-			'width'		=> '30',
-			'maxlength'	=> '255'
-		),
-		'quota_files' => array (
-			'datatype'	=> 'INTEGER',
-			'formtype'	=> 'TEXT',
-			'default'	=> '0',
-			'value'		=> '',
-			'width'		=> '7',
-			'maxlength'	=> '7'
-		),
-		'ul_ratio' => array (
-			'datatype'	=> 'INTEGER',
-			'formtype'	=> 'TEXT',
-			'default'	=> '0',
-			'value'		=> '',
-			'width'		=> '7',
-			'maxlength'	=> '7'
-		),
-		'dl_ratio' => array (
-			'datatype'	=> 'INTEGER',
-			'formtype'	=> 'TEXT',
-			'default'	=> '0',
-			'value'		=> '',
-			'width'		=> '7',
-			'maxlength'	=> '7'
-		),
-		'ul_bandwidth' => array (
-			'datatype'	=> 'INTEGER',
-			'formtype'	=> 'TEXT',
-			'default'	=> '0',
-			'value'		=> '',
-			'width'		=> '7',
-			'maxlength'	=> '7'
-		),
-		'dl_bandwidth' => array (
-			'datatype'	=> 'INTEGER',
-			'formtype'	=> 'TEXT',
-			'default'	=> '0',
-			'value'		=> '',
-			'width'		=> '7',
-			'maxlength'	=> '7'
-		),
-	##################################
-	# ENDE Datatable fields
-	##################################
-	)
+    'title'     => "Options",
+    'width'     => 100,
+    'template'  => "templates/ftp_user_advanced.htm",
+    'fields'    => array (
+    ##################################
+    # Begin Datatable fields
+    ##################################
+        'uid' => array (
+            'datatype'  => 'VARCHAR',
+            'formtype'  => 'TEXT',
+            'validators'    => array (  0 => array (    'type'  => 'NOTEMPTY',
+                                                        'errmsg'=> 'uid_error_empty'),
+                                    ),
+            'default'   => '0',
+            'value'     => '',
+            'width'     => '30',
+            'maxlength' => '255'
+        ),
+        'gid' => array (
+            'datatype'  => 'VARCHAR',
+            'formtype'  => 'TEXT',
+            'validators'    => array (  0 => array (    'type'  => 'NOTEMPTY',
+                                                        'errmsg'=> 'uid_error_empty'),
+                                    ),
+            'default'   => '0',
+            'value'     => '',
+            'width'     => '30',
+            'maxlength' => '255'
+        ),
+        'dir' => array (
+            'datatype'  => 'VARCHAR',
+            'formtype'  => 'TEXT',
+            'validators'    => array (  0 => array (    'type'  => 'NOTEMPTY',
+                                                        'errmsg'=> 'directory_error_empty'),
+                                    ),
+            'default'   => '',
+            'value'     => '',
+            'width'     => '30',
+            'maxlength' => '255'
+        ),
+        'quota_files' => array (
+            'datatype'  => 'INTEGER',
+            'formtype'  => 'TEXT',
+            'default'   => '0',
+            'value'     => '',
+            'width'     => '7',
+            'maxlength' => '7'
+        ),
+        'ul_ratio' => array (
+            'datatype'  => 'INTEGER',
+            'formtype'  => 'TEXT',
+            'default'   => '0',
+            'value'     => '',
+            'width'     => '7',
+            'maxlength' => '7'
+        ),
+        'dl_ratio' => array (
+            'datatype'  => 'INTEGER',
+            'formtype'  => 'TEXT',
+            'default'   => '0',
+            'value'     => '',
+            'width'     => '7',
+            'maxlength' => '7'
+        ),
+        'ul_bandwidth' => array (
+            'datatype'  => 'INTEGER',
+            'formtype'  => 'TEXT',
+            'default'   => '0',
+            'value'     => '',
+            'width'     => '7',
+            'maxlength' => '7'
+        ),
+        'dl_bandwidth' => array (
+            'datatype'  => 'INTEGER',
+            'formtype'  => 'TEXT',
+            'default'   => '0',
+            'value'     => '',
+            'width'     => '7',
+            'maxlength' => '7'
+        ),
+    ##################################
+    # ENDE Datatable fields
+    ##################################
+    )
+);
+
+} else {
+
+$form["tabs"]['advanced'] = array (
+    'title'     => "Options",
+    'width'     => 100,
+    'template'  => "templates/ftp_user_advanced_client.htm",
+    'fields'    => array (
+    ##################################
+    # Begin Datatable fields
+    ##################################
+        'dir' => array (
+            'datatype'  => 'VARCHAR',
+            'formtype'  => 'TEXT',
+            'validators'    => array (  0 => array (    'type'  => 'NOTEMPTY',
+                                                        'errmsg'=> 'directory_error_empty'),
+                                        1 => array (    'type'  => 'CUSTOM',
+                                                        'class' => 'validate_ftpuser',
+                                                        'function' => 'ftp_dir',
+                                                        'errmsg' => 'directory_error_notinweb'),
+                                    ),
+            'default'   => '',
+            'value'     => '',
+            'width'     => '30',
+            'maxlength' => '255'
+        ),
+    ##################################
+    # ENDE Datatable fields
+    ##################################
+    )
 );
 
 }
 
 
+
 ?>
\ No newline at end of file
diff --git a/interface/web/sites/lib/lang/de_database.lng b/interface/web/sites/lib/lang/de_database.lng
index 8224fa31292c1a0b707cc3cc1f2072fe54463c68..66241efa8761b2f01a44c4a0d866ea1fba54d2bd 100644
--- a/interface/web/sites/lib/lang/de_database.lng
+++ b/interface/web/sites/lib/lang/de_database.lng
@@ -6,6 +6,8 @@ $wb['database_user_txt'] = 'Datenbank Benutzer';
 $wb['database_password_txt'] = 'Datenbank Passwort';
 $wb['database_charset_txt'] = 'Datenbank Zeichensatz';
 $wb['remote_access_txt'] = 'Remotezugang';
+$wb['remote_ips_txt'] = 'Remotezugang-IPs (mit Komma trennen, keine Eingabe f&uuml;r <i>alle</i>)';
+$wb['database_remote_error_ips'] = 'Mindestens eine der eingegebenen IP Adressen ist ung&uuml;ltig.';
 $wb['client_txt'] = 'Kunde';
 $wb['active_txt'] = 'Aktiv';
 $wb['database_name_error_empty'] = 'Datenbankname ist leer.';
diff --git a/interface/web/sites/lib/lang/de_ftp_user.lng b/interface/web/sites/lib/lang/de_ftp_user.lng
index ea02532350b325cd1ab8ee22d1ba5059b66556d3..39912890baebed06700e2b1f559fe302ce230e97 100644
--- a/interface/web/sites/lib/lang/de_ftp_user.lng
+++ b/interface/web/sites/lib/lang/de_ftp_user.lng
@@ -22,4 +22,5 @@ $wb['quota_size_error_empty'] = 'Quota ist leer.';
 $wb['uid_error_empty'] = 'GID ist leer.';
 $wb['directory_error_empty'] = 'Verzeichniss ist leer.';
 $wb['password_strength_txt'] = 'Passwortkomplexität';
+$wb['directory_error_notinweb'] = 'Das Verzeichnis befindet sich nicht innerhalb des Verzeichnisses der Website.';
 ?>
diff --git a/interface/web/sites/lib/lang/en_database.lng b/interface/web/sites/lib/lang/en_database.lng
index df47802c955371b35a16bc75b80694d2e5e231b4..555511af3a3fcf5914e7c688c7ae7a7fa1fc104a 100644
--- a/interface/web/sites/lib/lang/en_database.lng
+++ b/interface/web/sites/lib/lang/en_database.lng
@@ -7,6 +7,8 @@ $wb["database_password_txt"] = 'Database password';
 $wb["password_strength_txt"] = 'Password strength';
 $wb["database_charset_txt"] = 'Database charset';
 $wb["remote_access_txt"] = 'Remote Access';
+$wb["remote_ips_txt"] = 'Remote Access IPs (separate by , and leave blank for <i>any</i>)';
+$wb["database_remote_error_ips"] = 'At least one of the entered ip addresses is invalid.';
 $wb["client_txt"] = 'Client';
 $wb["active_txt"] = 'Active';
 $wb["database_name_error_empty"] = 'Database name is empty.';
diff --git a/interface/web/sites/lib/lang/en_ftp_user.lng b/interface/web/sites/lib/lang/en_ftp_user.lng
index 691bc7d0f2d290d0acffefdd1333467958244ef8..f4554de80223af2f91ec910826d060eae0a6c34d 100644
--- a/interface/web/sites/lib/lang/en_ftp_user.lng
+++ b/interface/web/sites/lib/lang/en_ftp_user.lng
@@ -23,4 +23,5 @@ $wb["quota_size_error_empty"] = 'Quota is empty.';
 $wb["uid_error_empty"] = 'UID empty.';
 $wb["uid_error_empty"] = 'GID empty.';
 $wb["directory_error_empty"] = 'Directory empty.';
+$wb['directory_error_notinweb'] = 'Directory not inside of web root directory.';
 ?>
diff --git a/interface/web/sites/templates/database_edit.htm b/interface/web/sites/templates/database_edit.htm
index cb982f3854a860cb889e883d02765a90281391c7..da5ffecf1a2a9e5c74416c95ca3612729b460621 100644
--- a/interface/web/sites/templates/database_edit.htm
+++ b/interface/web/sites/templates/database_edit.htm
@@ -80,11 +80,15 @@
 		</tmpl_if>
       </div>
       <div class="ctrlHolder">
-				<p class="label">{tmpl_var name='remote_access_txt'}</p>
-					<div class="multiField">
-						{tmpl_var name='remote_access'}
-					</div>
-			</div>
+        <p class="label">{tmpl_var name='remote_access_txt'}</p>
+          <div class="multiField">
+            {tmpl_var name='remote_access'}
+          </div>
+      </div>
+      <div class="ctrlHolder">
+        <label for="remote_ips">{tmpl_var name='remote_ips_txt'}</label>
+        <input name="remote_ips" id="remote_ips" value="{tmpl_var name='remote_ips'}" size="60" type="text" class="textInput formLengthHalf" />
+      </div>
       <div class="ctrlHolder">
 				<p class="label">{tmpl_var name='active_txt'}</p>
 					<div class="multiField">
diff --git a/interface/web/sites/templates/ftp_user_advanced_client.htm b/interface/web/sites/templates/ftp_user_advanced_client.htm
new file mode 100644
index 0000000000000000000000000000000000000000..fd8d617cedc423822c51bb2570bad378e7631835
--- /dev/null
+++ b/interface/web/sites/templates/ftp_user_advanced_client.htm
@@ -0,0 +1,22 @@
+<h2><tmpl_var name="list_head_txt"></h2>
+<p><tmpl_var name="list_desc_txt"></p>
+
+<div class="panel panel_ftp_user">
+
+  <div class="pnl_formsarea">
+    <fieldset class="inlineLabels">
+      <div class="ctrlHolder">
+      	<label for="dir">{tmpl_var name='dir_txt'}</label>
+        <input name="dir" id="dir" value="{tmpl_var name='dir'}" size="30" maxlength="255" type="text" class="textInput" />
+			</div>
+    </fieldset>
+
+    <input type="hidden" name="id" value="{tmpl_var name='id'}">
+
+    <div class="buttonHolder buttons">
+      <button class="positive iconstxt icoPositive" type="button" value="{tmpl_var name='btn_save_txt'}" onClick="submitForm('pageForm','sites/ftp_user_edit.php');"><span>{tmpl_var name='btn_save_txt'}</span></button>
+      <button class="negative iconstxt icoNegative" type="button" value="{tmpl_var name='btn_cancel_txt'}" onClick="loadContent('sites/ftp_user_list.php');"><span>{tmpl_var name='btn_cancel_txt'}</span></button>
+    </div>
+  </div>
+  
+</div>
diff --git a/server/mods-available/web_module.inc.php b/server/mods-available/web_module.inc.php
index 9a3f85e413f5ea2e3b98a1a9e6d5399c67856aaa..1e7bde7f4bc04d2cc30b82e6e2120a394cf1df24 100644
--- a/server/mods-available/web_module.inc.php
+++ b/server/mods-available/web_module.inc.php
@@ -80,7 +80,8 @@ class web_module {
 		*/
 		
 		$app->modules->registerTableHook('web_domain','web_module','process');
-		$app->modules->registerTableHook('shell_user','web_module','process');
+        $app->modules->registerTableHook('ftp_user','web_module','process');
+        $app->modules->registerTableHook('shell_user','web_module','process');
 		
 		// Register service
 		$app->services->registerService('httpd','web_module','restartHttpd');
diff --git a/server/plugins-available/ftpuser_base_plugin.inc.php b/server/plugins-available/ftpuser_base_plugin.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..42edbb591c9e1f38c37ce77c9793c6d517403dc0
--- /dev/null
+++ b/server/plugins-available/ftpuser_base_plugin.inc.php
@@ -0,0 +1,112 @@
+<?php
+
+/*
+Copyright (c) 2007, Till Brehm, projektfarm Gmbh
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+class ftpuser_base_plugin {
+	
+	var $plugin_name = 'ftpuser_base_plugin';
+	var $class_name = 'ftpuser_base_plugin';
+	
+	//* This function is called during ispconfig installation to determine
+	//  if a symlink shall be created for this plugin.
+	function onInstall() {
+		global $conf;
+		
+		if($conf['services']['web'] == true) {
+			return true;
+		} else {
+			return false;
+		}
+		
+	}
+	
+		
+	/*
+	 	This function is called when the plugin is loaded
+	*/
+	
+	function onLoad() {
+		global $app;
+		
+		/*
+		Register for the events
+		*/
+		
+		$app->plugins->registerEvent('ftp_user_insert',$this->plugin_name,'insert');
+		$app->plugins->registerEvent('ftp_user_update',$this->plugin_name,'update');
+		$app->plugins->registerEvent('ftp_user_delete',$this->plugin_name,'delete');
+
+		
+	}
+	
+	
+	function insert($event_name,$data) {
+		global $app, $conf;
+		
+    if(!is_dir($data['new']['dir'])) {
+      $app->log("FTP User directory '".$data['new']['dir']."' does not exist. Creating it now.",LOGLEVEL_DEBUG);
+      
+      $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($data['new']['parent_domain_id']));
+      
+      exec('mkdir -p '.escapeshellcmd($data['new']['dir']));
+      exec('chown '.escapeshellcmd($web["system_user"]).':'.escapeshellcmd($web['system_group']).' '.$data['new']['dir']);
+      
+      $app->log("Added ftpuser_dir: ".$data['new']['dir'],LOGLEVEL_DEBUG);
+    }
+    
+	}
+	
+	function update($event_name,$data) {
+		global $app, $conf;
+		
+    if(!is_dir($data['new']['dir'])) {
+      $app->log("FTP User directory '".$data['new']['dir']."' does not exist. Creating it now.",LOGLEVEL_DEBUG);
+      
+      $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($data['new']['parent_domain_id']));
+      
+      exec('mkdir -p '.escapeshellcmd($data['new']['dir']));
+      exec('chown '.escapeshellcmd($web["system_user"]).':'.escapeshellcmd($web['system_group']).' '.$data['new']['dir']);
+      
+      $app->log("Added ftpuser_dir: ".$data['new']['dir'],LOGLEVEL_DEBUG);
+    }
+	}
+	
+	function delete($event_name,$data) {
+		global $app, $conf;
+		
+    $app->log("Ftpuser:".$data['new']['username']." deleted.",LOGLEVEL_DEBUG);
+		
+	}
+	
+	
+	
+
+} // end class
+
+?>
\ No newline at end of file
diff --git a/server/plugins-available/mysql_clientdb_plugin.inc.php b/server/plugins-available/mysql_clientdb_plugin.inc.php
index eaf85e717dd70b9a1a5e1d4e3e0019c50238b818..493d80fb6ee8c9a1cbc5148f40d16dff891c3462 100644
--- a/server/plugins-available/mysql_clientdb_plugin.inc.php
+++ b/server/plugins-available/mysql_clientdb_plugin.inc.php
@@ -66,6 +66,52 @@ class mysql_clientdb_plugin {
 		
 	}
 	
+  function process_host_list($action, $database_name, $database_user, $database_password, $host_list, $link, $database_rename_user = "") {
+      global $app;
+      
+      $action = strtoupper($action);
+      
+      // set to all hosts if none given
+      if(trim($host_list) == "") $host_list = "%";
+      
+      // process arrays and comma separated strings
+      if(!is_array($host_list)) $host_list = split(",", $host_list);
+      
+      $success = true;
+      
+      // loop through hostlist
+      foreach($host_list as $db_host) {
+          $db_host = trim($db_host);
+          
+          // check if entry is valid ip address
+          $valid = true;
+          if(preg_match("/^[0-9]{1,3}(\.)[0-9]{1,3}(\.)[0-9]{1,3}(\.)[0-9]{1,3}$/", $db_host)) {
+              $groups = explode(".", $db_host);
+              foreach($groups as $group){
+                if($group<0 OR $group>255)
+                $valid=false;
+              }
+          } else {
+              $valid = false;
+          }
+          
+          if($valid == false) continue;
+          
+          if($action == "GRANT") {
+              if(!mysql_query("GRANT ALL ON ".mysql_real_escape_string($database_name,$link).".* TO '".mysql_real_escape_string($database_user,$link)."'@'$db_host' IDENTIFIED BY '".mysql_real_escape_string($database_password,$link)."';",$link)) $success = false;
+          } elseif($action == "REVOKE") {
+              //mysql_query("REVOKE ALL PRIVILEGES ON ".mysql_real_escape_string($database_name,$link).".* FROM '".mysql_real_escape_string($database_user,$link)."';",$link);
+          } elseif($action == "DROP") {
+              if(!mysql_query("DROP USER '".mysql_real_escape_string($database_user,$link)."'@'$db_host';",$link)) $success = false;
+          } elseif($action == "RENAME") {
+              if(!mysql_query("RENAME USER '".mysql_real_escape_string($database_user,$link)."'@'$db_host' TO '".mysql_real_escape_string($database_rename_user,$link)."'@'$db_host'",$link)) $success = false;
+          } elseif($action == "PASSWORD") {
+              if(!mysql_query("SET PASSWORD FOR '".mysql_real_escape_string($database_user,$link)."'@'$db_host' = PASSWORD('".mysql_real_escape_string($database_password,$link)."');",$link)) $success = false;
+          }
+      }
+      
+      return $success;
+  }
 	
 	function db_insert($event_name,$data) {
 		global $app, $conf;
@@ -101,8 +147,7 @@ class mysql_clientdb_plugin {
 			if($data["new"]["active"] == 'y') {
 				
 				if($data["new"]["remote_access"] == 'y') {
-			 		$db_host = '%';
-					mysql_query("GRANT ALL ON ".mysql_real_escape_string($data["new"]["database_name"],$link).".* TO '".mysql_real_escape_string($data["new"]["database_user"],$link)."'@'$db_host' IDENTIFIED BY '".mysql_real_escape_string($data["new"]["database_password"],$link)."';",$link);
+          $this->process_host_list("GRANT", $data["new"]["database_name"], $data["new"]["database_user"], $data["new"]["database_password"], $data["new"]["remote_ips"], $link);
 				}
 				
 				$db_host = 'localhost';
@@ -136,8 +181,7 @@ class mysql_clientdb_plugin {
 			if($data["new"]["active"] == 'y' && $data["old"]["active"] == 'n') {
 				
 				if($data["new"]["remote_access"] == 'y') {
-			 		$db_host = '%';
-					mysql_query("GRANT ALL ON ".mysql_real_escape_string($data["new"]["database_name"],$link).".* TO '".mysql_real_escape_string($data["new"]["database_user"],$link)."'@'$db_host' IDENTIFIED BY '".mysql_real_escape_string($data["new"]["database_password"],$link)."';",$link);
+          $this->process_host_list("GRANT", $data["new"]["database_name"], $data["new"]["database_user"], $data["new"]["database_password"], $data["new"]["remote_ips"], $link);
 				}
 				
 				$db_host = 'localhost';
@@ -151,8 +195,7 @@ class mysql_clientdb_plugin {
 			if($data["new"]["active"] == 'n' && $data["old"]["active"] == 'y') {
 				
 				if($data["old"]["remote_access"] == 'y') {
-			 		$db_host = '%';
-					mysql_query("DROP USER '".mysql_real_escape_string($data["old"]["database_user"],$link)."'@'$db_host';",$link);
+          $this->process_host_list("DROP", "", $data["old"]["database_user"], "", $data["old"]["remote_ips"], $link);
 				}
 				
 				$db_host = 'localhost';
@@ -167,8 +210,7 @@ class mysql_clientdb_plugin {
 				$db_host = 'localhost';
 				mysql_query("RENAME USER '".mysql_real_escape_string($data["old"]["database_user"],$link)."'@'$db_host' TO '".mysql_real_escape_string($data["new"]["database_user"],$link)."'@'$db_host'",$link);
 				if($data["old"]["remote_access"] == 'y') {
-					$db_host = '%';
-					mysql_query("RENAME USER '".mysql_real_escape_string($data["old"]["database_user"],$link)."'@'$db_host' TO '".mysql_real_escape_string($data["new"]["database_user"],$link)."'@'$db_host'",$link);
+          $this->process_host_list("RENAME", "", $data["new"]["database_user"], "", $data["new"]["remote_ips"], $link, $data["new"]["database_user"]);
 				}
 				$app->log('Renaming mysql user: '.$data["old"]["database_user"].' to '.$data["new"]["database_user"],LOGLEVEL_DEBUG);
 			}
@@ -180,23 +222,25 @@ class mysql_clientdb_plugin {
 				//mysql_query("REVOKE ALL PRIVILEGES ON ".mysql_real_escape_string($data["new"]["database_name"],$link).".* FROM '".mysql_real_escape_string($data["new"]["database_user"],$link)."';",$link);
 				
 				//* set new priveliges
-				$db_host = '%';
 				if($data["new"]["remote_access"] == 'y') { 		
-					mysql_query("GRANT ALL ON ".mysql_real_escape_string($data["new"]["database_name"],$link).".* TO '".mysql_real_escape_string($data["new"]["database_user"],$link)."'@'$db_host' IDENTIFIED BY '".mysql_real_escape_string($data["new"]["database_password"],$link)."';",$link);
+					$this->process_host_list("GRANT", $data["new"]["database_name"], $data["new"]["database_user"], $data["new"]["database_password"], $data["new"]["remote_ips"], $link);
 				} else {
-					mysql_query("DROP USER '".mysql_real_escape_string($data["old"]["database_user"],$link)."'@'$db_host';",$link);
+					$this->process_host_list("DROP", "", $data["old"]["database_user"], "", $data["old"]["remote_ips"], $link);
 				}
 				$app->log('Changing mysql remote access priveliges for database: '.$data["new"]["database_name"],LOGLEVEL_DEBUG);
-			}
-			
+			} elseif($data["new"]["remote_access"] == 'y' && $data["new"]["remote_ips"] != $data["old"]["remote_ips"]) {
+          //* Change remote access list
+          $this->process_host_list("DROP", "", $data["old"]["database_user"], "", $data["old"]["remote_ips"], $link);
+          $this->process_host_list("GRANT", $data["new"]["database_name"], $data["new"]["database_user"], $data["new"]["database_password"], $data["new"]["remote_ips"], $link);
+      }
+      
 			//* Change password
 			if($data["new"]["database_password"] != $data["old"]["database_password"]) {
 				$db_host = 'localhost';
 				mysql_query("SET PASSWORD FOR '".mysql_real_escape_string($data["new"]["database_user"],$link)."'@'$db_host' = PASSWORD('".mysql_real_escape_string($data["new"]["database_password"],$link)."');",$link);
 
 				if($data["new"]["remote_access"] == 'y') {
-					$db_host = '%';
-					mysql_query("SET PASSWORD FOR '".mysql_real_escape_string($data["new"]["database_user"],$link)."'@'$db_host' = PASSWORD('".mysql_real_escape_string($data["new"]["database_password"],$link)."');",$link);
+          $this->process_host_list("PASSWORD", "", $data["new"]["database_user"], $data["new"]["database_password"], $data["new"]["remote_ips"], $link);
 				}
 				$app->log('Changing mysql user password for: '.$data["new"]["database_user"],LOGLEVEL_DEBUG);
 			}
@@ -225,9 +269,8 @@ class mysql_clientdb_plugin {
 			
 			//* Get the db host setting for the access priveliges
 			if($data["old"]["remote_access"] == 'y') {
-			 	$db_host = '%';
-				if(mysql_query("DROP USER '".mysql_real_escape_string($data["old"]["database_user"],$link)."'@'$db_host';",$link)) {
-					$app->log('Dropping mysql user: '.$data["old"]["database_user"],LOGLEVEL_DEBUG);
+			 	if($this->process_host_list("DROP", "", $data["old"]["database_user"], "", $data["old"]["remote_ips"], $link)) {
+        	$app->log('Dropping mysql user: '.$data["old"]["database_user"],LOGLEVEL_DEBUG);
 				} else {
 					$app->log('Error while dropping mysql user: '.$data["old"]["database_user"].' '.mysql_error($link),LOGLEVEL_ERROR);
 				}
diff --git a/server/plugins-available/pma_symlink_plugin.inc.php b/server/plugins-available/pma_symlink_plugin.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..06ce78722a687b83ffb141c60bede31afab674c8
--- /dev/null
+++ b/server/plugins-available/pma_symlink_plugin.inc.php
@@ -0,0 +1,121 @@
+<?php
+
+/*
+Copyright (c) 2007 - 2009, Till Brehm, projektfarm Gmbh
+All rights reserved.
+Modification (c) 2009, Marius Cramer, pixcept KG 
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+class pma_symlink_plugin {
+	
+	var $plugin_name = 'pma_symlink_plugin';
+	var $class_name = 'pma_symlink_plugin';
+	
+    var $action;
+    
+	//* This function is called during ispconfig installation to determine
+	//  if a symlink shall be created for this plugin.
+	function onInstall() {
+		global $conf;
+		
+			return false;
+		
+	}
+	
+		
+	/*
+	 	This function is called when the plugin is loaded
+	*/
+	
+	function onLoad() {
+		global $app;
+		
+		/*
+		Register for the events
+		*/
+		
+		$app->plugins->registerEvent('web_domain_insert',$this->plugin_name,'insert');
+		$app->plugins->registerEvent('web_domain_update',$this->plugin_name,'update');
+	}
+	
+	function insert($event_name,$data) {
+		global $app, $conf;
+		
+		$this->action = 'insert';
+		// just run the update function
+		$this->update($event_name,$data);
+	}
+	
+	function update($event_name,$data) {
+		global $app, $conf;
+		
+		if($this->action != 'insert') $this->action = 'update';
+		
+		if($data["new"]["type"] != "vhost" && $data["new"]["parent_domain_id"] > 0) {
+			
+			$old_parent_domain_id = intval($data["old"]["parent_domain_id"]);
+			$new_parent_domain_id = intval($data["new"]["parent_domain_id"]);
+			
+			// If the parent_domain_id has been chenged, we will have to update the old site as well.
+			if($this->action == 'update' && $data["new"]["parent_domain_id"] != $data["old"]["parent_domain_id"]) {
+				$tmp = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$old_parent_domain_id." AND active = 'y'");
+				$data["new"] = $tmp;
+				$data["old"] = $tmp;
+				$this->action = 'update';
+				$this->update($event_name,$data);
+			}
+			
+			// This is not a vhost, so we need to update the parent record instead.
+			$tmp = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$new_parent_domain_id." AND active = 'y'");
+			$data["new"] = $tmp;
+			$data["old"] = $tmp;
+			$this->action = 'update';
+		}
+		
+		if($data["new"]["document_root"] == '') {
+			$app->log("document_root not set",LOGLEVEL_WARN);
+			return 0;
+		}
+		
+        $symlink = true;
+        if($data["new"]["php"] == "suphp") $symlink = false;
+        elseif($data["new"]["php"] == "cgi" && $data["new"]["suexec"] == "y") $symlink = false;
+        elseif($data["new"]["php"] == "fast-cgi" && $data["new"]["suexec"] == "y") $symlink = false;
+        
+        
+        if(!is_dir($data["new"]["document_root"]."/web")) exec("mkdir -p ".$data["new"]["document_root"]."/web");
+        if($symlink == false) {
+            if(is_link($data["new"]["document_root"]."/web/phpmyadmin")) exec("rm -f ".$data["new"]["document_root"]."/web/phpmyadmin");
+        } else {
+            if(!is_link($data["new"]["document_root"]."/web/phpmyadmin")) exec("ln -s /var/www/phpmyadmin ".$data["new"]["document_root"]."/web/phpmyadmin");
+            else exec("ln -sf /var/www/phpmyadmin ".$data["new"]["document_root"]."/web/phpmyadmin");
+        }
+	}
+	
+
+} // end class
+
+?>
\ No newline at end of file
diff --git a/server/plugins-available/software_update_plugin.inc.php b/server/plugins-available/software_update_plugin.inc.php
index bf6aa56c10a9860d9a89f3623192dfe79617fa16..f3b0c32442d209b3382238a37ee302ca07b10762 100644
--- a/server/plugins-available/software_update_plugin.inc.php
+++ b/server/plugins-available/software_update_plugin.inc.php
@@ -62,17 +62,24 @@ class software_update_plugin {
 		
 	}
 	
-	
+	function set_install_status($inst_id, $status) {
+        global $app;
+        
+        $app->db->query("UPDATE software_update_inst SET status = '{$status}' WHERE software_update_inst_id = '{$inst_id}'");
+        $app->dbmaster->query("UPDATE software_update_inst SET status = '{$status}' WHERE software_update_inst_id = '{$inst_id}'");
+    }
+    
 	function process($event_name,$data) {
 		global $app, $conf;
 		
-		if(!$conf['software_updates_enabled'] == true) {
+        if(!$conf['software_updates_enabled'] == true) {
 			$app->log('Software Updates not enabled on this server. To enable updates, set $conf["software_updates_enabled"] = true; in config.inc.php',LOGLEVEL_ERROR);
+            $this->set_install_status($data["new"]["software_update_inst_id"], "failed");
 			return false;
 		}
 		
 		//* Get the info of the package:
-		$software_update_id = intval($data["new"]["software_update_id"]);
+        $software_update_id = intval($data["new"]["software_update_id"]);
 		$software_update = $app->db->queryOneRecord("SELECT * FROM software_update WHERE software_update_id = '$software_update_id'");
 		
 		$temp_dir = '/tmp/'.md5 (uniqid (rand()));
@@ -80,6 +87,7 @@ class software_update_plugin {
 		mkdir($temp_dir);
 		if(!is_dir($temp_dir)) {
 			$app->log("Unable to create temp directory.",LOGLEVEL_ERROR);
+            $this->set_install_status($data["new"]["software_update_inst_id"], "failed");
 			return false;
 		}
 		
@@ -97,6 +105,7 @@ class software_update_plugin {
 				$app->log("The md5 sum of the downloaded file is incorrect. Update aborted.",LOGLEVEL_ERROR);
 				exec("rm -rf $temp_dir");
 				$app->log("Deleting the temp directory $temp_dir",LOGLEVEL_DEBUG);
+                $this->set_install_status($data["new"]["software_update_inst_id"], "failed");
 				return false;
 			} else {
 				$app->log("md5sum of the downloaded file is verified.",LOGLEVEL_DEBUG);
@@ -110,18 +119,28 @@ class software_update_plugin {
 				// Execute the setup script
 				exec('chmod +x '.$temp_dir.'/setup.sh');
 				$app->log("Executing setup.sh file in directory $temp_dir",LOGLEVEL_DEBUG);
-				exec('cd '.$temp_dir.' && ./setup.sh');
-				$app->db->query("UPDATE software_update_inst SET status = 'installed' WHERE software_update_inst_id = ".$data["new"]["software_update_inst_id"]);
+				exec('cd '.$temp_dir.' && ./setup.sh > package_install.log');
+                
+                $log_data = @file_get_contents("{$temp_dir}/package_install.log");
+                if(preg_match("'.*\[OK\]\s*$'is", $log_data)) {
+                    $app->log("Installation successful",LOGLEVEL_DEBUG);
+                    $app->log($log_data,LOGLEVEL_DEBUG);
+                    $this->set_install_status($data["new"]["software_update_inst_id"], "installed");
+                } else {
+                    $app->log("Installation failed:\n\n" . $log_data,LOGLEVEL_ERROR);
+                    $this->set_install_status($data["new"]["software_update_inst_id"], "failed");
+                }
 			} else {
 				$app->log("setup.sh file not found",LOGLEVEL_ERROR);
+                $this->set_install_status($data["new"]["software_update_inst_id"], "failed");
 			}
 		} else {
 			$app->log("Download of the update file failed",LOGLEVEL_ERROR);
+            $this->set_install_status($data["new"]["software_update_inst_id"], "failed");
 		}
 		
 		exec("rm -rf $temp_dir");
 		$app->log("Deleting the temp directory $temp_dir",LOGLEVEL_DEBUG);
-		
 	}
 	
 
diff --git a/server/plugins-available/webmail_symlink_plugin.inc.php b/server/plugins-available/webmail_symlink_plugin.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..db5fd266e5fb37ab9bb0a10b3ebeb0d8b14fec5f
--- /dev/null
+++ b/server/plugins-available/webmail_symlink_plugin.inc.php
@@ -0,0 +1,121 @@
+<?php
+
+/*
+Copyright (c) 2007 - 2009, Till Brehm, projektfarm Gmbh
+All rights reserved.
+Modification (c) 2009, Marius Cramer, pixcept KG 
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+class webmail_symlink_plugin {
+	
+	var $plugin_name = 'webmail_symlink_plugin';
+	var $class_name = 'webmail_symlink_plugin';
+	
+    var $action;
+    
+	//* This function is called during ispconfig installation to determine
+	//  if a symlink shall be created for this plugin.
+	function onInstall() {
+		global $conf;
+		
+        return false;
+		
+	}
+	
+		
+	/*
+	 	This function is called when the plugin is loaded
+	*/
+	
+	function onLoad() {
+		global $app;
+		
+		/*
+		Register for the events
+		*/
+		
+		$app->plugins->registerEvent('web_domain_insert',$this->plugin_name,'insert');
+		$app->plugins->registerEvent('web_domain_update',$this->plugin_name,'update');
+	}
+	
+	function insert($event_name,$data) {
+		global $app, $conf;
+		
+		$this->action = 'insert';
+		// just run the update function
+		$this->update($event_name,$data);
+	}
+	
+	function update($event_name,$data) {
+		global $app, $conf;
+		
+		if($this->action != 'insert') $this->action = 'update';
+		
+		if($data["new"]["type"] != "vhost" && $data["new"]["parent_domain_id"] > 0) {
+			
+			$old_parent_domain_id = intval($data["old"]["parent_domain_id"]);
+			$new_parent_domain_id = intval($data["new"]["parent_domain_id"]);
+			
+			// If the parent_domain_id has been chenged, we will have to update the old site as well.
+			if($this->action == 'update' && $data["new"]["parent_domain_id"] != $data["old"]["parent_domain_id"]) {
+				$tmp = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$old_parent_domain_id." AND active = 'y'");
+				$data["new"] = $tmp;
+				$data["old"] = $tmp;
+				$this->action = 'update';
+				$this->update($event_name,$data);
+			}
+			
+			// This is not a vhost, so we need to update the parent record instead.
+			$tmp = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".$new_parent_domain_id." AND active = 'y'");
+			$data["new"] = $tmp;
+			$data["old"] = $tmp;
+			$this->action = 'update';
+		}
+		
+		if($data["new"]["document_root"] == '') {
+			$app->log("document_root not set",LOGLEVEL_WARN);
+			return 0;
+		}
+		
+        $symlink = true;
+        if($data["new"]["php"] == "suphp") $symlink = false;
+        elseif($data["new"]["php"] == "cgi" && $data["new"]["suexec"] == "y") $symlink = false;
+        elseif($data["new"]["php"] == "fast-cgi" && $data["new"]["suexec"] == "y") $symlink = false;
+        
+        
+        if(!is_dir($data["new"]["document_root"]."/web")) exec("mkdir -p ".$data["new"]["document_root"]."/web");
+        if($symlink == false) {
+            if(is_link($data["new"]["document_root"]."/web/webmail")) exec("rm -f ".$data["new"]["document_root"]."/web/webmail");
+        } else {
+            if(!is_link($data["new"]["document_root"]."/web/webmail")) exec("ln -s /var/www/webmail ".$data["new"]["document_root"]."/web/webmail");
+            else exec("ln -sf /var/www/webmail ".$data["new"]["document_root"]."/web/webmail");
+        }
+	}
+	
+
+} // end class
+
+?>
\ No newline at end of file