From 381520c8866a5f3be7e743e3ae16b6fb2988c495 Mon Sep 17 00:00:00 2001
From: mcramer <m.cramer@pixcept.de>
Date: Tue, 21 Aug 2012 13:51:27 -0400
Subject: [PATCH] Implemented  FS#1448 - one database user name and multiple databases  Bugfix on db-Class (datalog Update)

---
 interface/web/sites/templates/database_user_admin_list.htm   |   50 ++
 interface/web/sites/templates/database_user_edit.htm         |   54 ++
 interface/web/sites/templates/database_edit.htm              |   24 
 interface/web/sites/lib/module.conf.php                      |    3 
 interface/web/sites/form/database_user.tform.php             |   99 ++++
 interface/web/sites/database_del.php                         |   37 +
 interface/web/sites/database_edit.php                        |  158 +++++-
 interface/web/sites/lib/lang/de_database_list.lng            |    2 
 interface/web/sites/database_user_del.php                    |   78 +++
 interface/web/sites/lib/lang/de_database.lng                 |    8 
 server/plugins-available/mysql_clientdb_plugin.inc.php       |  289 ++++++++---
 install/sql/ispconfig3.sql                                   |   26 
 server/mods-available/database_module.inc.php                |   11 
 interface/web/sites/database_user_list.php                   |   65 ++
 interface/web/sites/database_user_edit.php                   |  212 ++++++++
 interface/web/sites/form/database.tform.php                  |   40 
 install/sql/incremental/upd_0039.sql                         |   36 +
 interface/web/sites/lib/lang/en_database_user_admin_list.lng |    6 
 interface/lib/classes/db_mysql.inc.php                       |    2 
 interface/web/sites/templates/database_list.htm              |    6 
 interface/web/sites/lib/lang/en_database.lng                 |    4 
 interface/web/sites/templates/database_user_list.htm         |   51 ++
 interface/web/sites/lib/lang/de_database_user_admin_list.lng |    6 
 interface/web/sites/lib/lang/en_database_user.lng            |   23 
 interface/web/sites/lib/lang/de_database_user_list.lng       |    5 
 interface/web/sites/list/database_user.list.php              |   81 +++
 interface/web/sites/lib/lang/de_database_user.lng            |   23 
 interface/web/sites/lib/lang/en_database_user_list.lng       |    5 
 interface/web/sites/list/database.list.php                   |   17 
 interface/web/sites/lib/remote.conf.php                      |    2 
 interface/web/sites/templates/database_admin_list.htm        |    4 
 31 files changed, 1,244 insertions(+), 183 deletions(-)

diff --git a/install/sql/incremental/upd_0039.sql b/install/sql/incremental/upd_0039.sql
new file mode 100644
index 0000000..b5f6463
--- /dev/null
+++ b/install/sql/incremental/upd_0039.sql
@@ -0,0 +1,36 @@
+-- --------------------------------------------------------
+
+--
+-- Tabellenstruktur für Tabelle `web_database_user`
+--
+
+CREATE TABLE IF NOT EXISTS `web_database_user` (
+  `database_user_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `sys_userid` int(11) unsigned NOT NULL DEFAULT '0',
+  `sys_groupid` int(11) unsigned NOT NULL DEFAULT '0',
+  `sys_perm_user` varchar(5) DEFAULT NULL,
+  `sys_perm_group` varchar(5) DEFAULT NULL,
+  `sys_perm_other` varchar(5) DEFAULT NULL,
+  `server_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
+  `database_user` varchar(64) DEFAULT NULL,
+  `database_password` varchar(64) DEFAULT NULL,
+  PRIMARY KEY (`database_user_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+ALTER TABLE `web_database` ADD `database_user_id` INT( 11 ) UNSIGNED NULL DEFAULT NULL AFTER `database_password` ,
+ADD `database_ro_user_id` INT( 11 ) UNSIGNED NULL DEFAULT NULL AFTER `database_user_id`,
+ADD INDEX ( `database_user_id` ),
+ADD INDEX ( `database_ro_user_id` ) ;
+
+-- --------------------------------------------------------
+
+UPDATE `web_database`, `web_database_user` SET `web_database`.`database_user_id` = `web_database_user`.`database_user_id` WHERE `web_database_user`.`database_user` = `web_database`.`database_user`;
+
+-- --------------------------------------------------------
+
+ALTER TABLE `web_database`
+DROP `database_user`,
+DROP `database_password`;
+
diff --git a/install/sql/ispconfig3.sql b/install/sql/ispconfig3.sql
index 58e0178..5777459 100644
--- a/install/sql/ispconfig3.sql
+++ b/install/sql/ispconfig3.sql
@@ -1616,19 +1616,39 @@
   `parent_domain_id` int(11) unsigned NOT NULL DEFAULT  '0',
   `type` varchar(16) NOT NULL DEFAULT 'y',
   `database_name` varchar(64) DEFAULT NULL,
-  `database_user` varchar(64) DEFAULT NULL,
-  `database_password` varchar(64) DEFAULT NULL,
+  `database_user_id` int(11) unsigned DEFAULT NULL,
+  `database_ro_user_id` int(11) unsigned DEFAULT NULL,
   `database_charset` varchar(64) DEFAULT NULL,
   `remote_access` enum('n','y') NOT NULL DEFAULT 'y',
   `remote_ips` text NOT NULL,
   `backup_interval` VARCHAR( 255 ) NOT NULL DEFAULT 'none',
   `backup_copies` INT NOT NULL DEFAULT '1',
   `active` enum('n','y') NOT NULL DEFAULT 'y',
-  PRIMARY KEY (`database_id`)
+  PRIMARY KEY (`database_id`),
+  KEY `database_user_id` (`database_user_id`),
+  KEY `database_ro_user_id` (`database_ro_user_id`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 
 -- --------------------------------------------------------
 
+--
+-- Tabellenstruktur für Tabelle `web_database_user`
+--
+
+CREATE TABLE IF NOT EXISTS `web_database_user` (
+  `database_user_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `sys_userid` int(11) unsigned NOT NULL DEFAULT '0',
+  `sys_groupid` int(11) unsigned NOT NULL DEFAULT '0',
+  `sys_perm_user` varchar(5) DEFAULT NULL,
+  `sys_perm_group` varchar(5) DEFAULT NULL,
+  `sys_perm_other` varchar(5) DEFAULT NULL,
+  `database_user` varchar(64) DEFAULT NULL,
+  `database_password` varchar(64) DEFAULT NULL,
+  PRIMARY KEY (`database_user_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
 -- 
 -- Table structure for table  `web_domain`
 -- 
diff --git a/interface/lib/classes/db_mysql.inc.php b/interface/lib/classes/db_mysql.inc.php
index 8354be6..e2232e8 100644
--- a/interface/lib/classes/db_mysql.inc.php
+++ b/interface/lib/classes/db_mysql.inc.php
@@ -299,7 +299,7 @@
 			$update_data_str = $update_data;
 		}
 		
-      $this->query("UPDATE $tablename SET $update_data WHERE $index_field = '$index_value'");
+      $this->query("UPDATE $tablename SET $update_data_str WHERE $index_field = '$index_value'");
       $new_rec = $this->queryOneRecord("SELECT * FROM $tablename WHERE $index_field = '$index_value'");
       $this->datalogSave($tablename, 'UPDATE', $index_field, $index_value, $old_rec, $new_rec, $force_update);
 
diff --git a/interface/web/sites/database_del.php b/interface/web/sites/database_del.php
index f5f8dbc..b1263b3 100644
--- a/interface/web/sites/database_del.php
+++ b/interface/web/sites/database_del.php
@@ -46,6 +46,41 @@
 $app->auth->check_module_permissions('sites');
 
 $app->uses("tform_actions");
-$app->tform_actions->onDelete();
+class page_action extends tform_actions {
+	function onBeforeDelete() {
+		global $app; $conf;
+		if($app->tform->checkPerm($this->id,'d') == false) $app->error($app->lng('error_no_delete_permission'));
+        
+        $old_record = $app->tform->getDataRecord($this->id);
+        if($old_record['database_user_id']) {
+            // check if any database on the server still uses this one
+            $check = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `server_id` = '" . intval($old_record['server_id']) . "' AND (`database_user_id` = '" . intval($old_record['database_user_id']) . "' OR `database_ro_user_id` = '" . intval($old_record['database_user_id']) . "') AND `sys_groupid` = '" . intval($old_record['sys_groupid']) . "' AND `database_id` != '" . intval($this->id) . "'");
+            if($check['cnt'] < 1) {
+                // send a datalog delete
+                $db_user = $app->db->queryOneRecord("SELECT * FROM `web_database_user` WHERE `database_user_id` = '" . intval($old_record['database_user_id']) . "' AND `sys_groupid` = '" . intval($old_record['sys_groupid']) . "'");
+                if($db_user) {
+                    $db_user['server_id'] = $old_record['server_id'];
+                    $app->db->datalogSave('web_database_user', 'DELETE', 'database_user_id', $db_user['database_user_id'], $db_user, array());
+                }
+            }
+        }
+        if($old_record['database_ro_user_id']) {
+            // check if any database on the server still uses this one
+            $check = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `server_id` = '" . intval($old_record['server_id']) . "' AND (`database_user_id` = '" . intval($old_record['database_ro_user_id']) . "' OR `database_ro_user_id` = '" . intval($old_record['database_ro_user_id']) . "') AND `sys_groupid` = '" . intval($old_record['sys_groupid']) . "' AND `database_id` != '" . intval($this->id) . "'");
+            if($check['cnt'] < 1) {
+                // send a datalog delete
+                $db_user = $app->db->queryOneRecord("SELECT * FROM `web_database_user` WHERE `database_user_id` = '" . intval($old_record['database_ro_user_id']) . "' AND `sys_groupid` = '" . intval($old_record['sys_groupid']) . "'");
+                if($db_user) {
+                    $db_user['server_id'] = $old_record['server_id'];
+                    $app->db->datalogSave('web_database_user', 'DELETE', 'database_user_id', $db_user['database_user_id'], $db_user, array());
+                }
+            }
+        }
+        
+	}
+}
+
+$page = new page_action;
+$page->onDelete();
 
 ?>
\ No newline at end of file
diff --git a/interface/web/sites/database_edit.php b/interface/web/sites/database_edit.php
index 7f675fa..d098625 100644
--- a/interface/web/sites/database_edit.php
+++ b/interface/web/sites/database_edit.php
@@ -114,20 +114,16 @@
 		$app->uses('getconf');
 		$global_config = $app->getconf->get_global_config('sites');
 		$dbname_prefix = replacePrefix($global_config['dbname_prefix'], $this->dataRecord);
-		$dbuser_prefix = replacePrefix($global_config['dbuser_prefix'], $this->dataRecord);
 		
 		if ($this->dataRecord['database_name'] != ""){
 			/* REMOVE the restriction */
 			$app->tpl->setVar("database_name", str_replace($dbname_prefix , '', $this->dataRecord['database_name']));
-			$app->tpl->setVar("database_user", str_replace($dbuser_prefix , '', $this->dataRecord['database_user']));
 		}
 		
 		if($_SESSION["s"]["user"]["typ"] == 'admin' || $app->auth->has_clients($_SESSION['s']['user']['userid'])) {
 			$app->tpl->setVar("database_name_prefix", $global_config['dbname_prefix']);
-			$app->tpl->setVar("database_user_prefix", $global_config['dbuser_prefix']);
 		} else {
 			$app->tpl->setVar("database_name_prefix", $dbname_prefix);
-			$app->tpl->setVar("database_user_prefix", $dbuser_prefix);
 		}
 		
 		if($this->id > 0) {
@@ -187,8 +183,7 @@
 		$app->uses('getconf');
 		$global_config = $app->getconf->get_global_config('sites');
 		$dbname_prefix = replacePrefix($global_config['dbname_prefix'], $this->dataRecord);
-		$dbuser_prefix = replacePrefix($global_config['dbuser_prefix'], $this->dataRecord);
-
+		
 		//* Prevent that the database name and charset is changed
 		$old_record = $app->tform->getDataRecord($this->id);
 		if($old_record["database_name"] != $dbname_prefix . $this->dataRecord["database_name"]) {
@@ -200,8 +195,7 @@
 		
 		//* Database username and database name shall not be empty
 		if($this->dataRecord['database_name'] == '') $app->tform->errorMessage .= $app->tform->wordbook["database_name_error_empty"].'<br />';
-		if($this->dataRecord['database_user'] == '') $app->tform->errorMessage .= $app->tform->wordbook["database_user_error_empty"].'<br />';
-
+		
 		//* Check if the server has been changed
 		// We do this only for the admin or reseller users, as normal clients can not change the server ID anyway
 		if($_SESSION["s"]["user"]["typ"] == 'admin' || $app->auth->has_clients($_SESSION['s']['user']['userid'])) {
@@ -214,7 +208,6 @@
 		unset($old_record);
 		
 		if(strlen($dbname_prefix . $this->dataRecord['database_name']) > 64) $app->tform->errorMessage .= str_replace('{db}',$dbname_prefix . $this->dataRecord['database_name'],$app->tform->wordbook["database_name_error_len"]).'<br />';
-		if(strlen($dbuser_prefix . $this->dataRecord['database_user']) > 16) $app->tform->errorMessage .= str_replace('{user}',$dbuser_prefix . $this->dataRecord['database_user'],$app->tform->wordbook["database_user_error_len"]).'<br />';
 		
 		//* Check database name and user against blacklist
 		$dbname_blacklist = array($conf['db_database'],'mysql');
@@ -222,16 +215,10 @@
 			$app->tform->errorMessage .= $app->lng('Database name not allowed.').'<br />';
 		}
 		
-		$dbuser_blacklist = array($conf['db_user'],'mysql','root');
-		if(in_array($dbname_prefix . $this->dataRecord['database_user'],$dbname_blacklist)) {
-			$app->tform->errorMessage .= $app->lng('Database user not allowed.').'<br />';
-		}
-		
 		if ($app->tform->errorMessage == ''){
 			/* restrict the names if there is no error */
             /* crop user and db names if they are too long -> mysql: user: 16 chars / db: 64 chars */
 			$this->dataRecord['database_name'] = substr($dbname_prefix . $this->dataRecord['database_name'], 0, 64);
-			$this->dataRecord['database_user'] = substr($dbuser_prefix . $this->dataRecord['database_user'], 0, 16);
 		}
 		
 		//* Check for duplicates
@@ -244,8 +231,11 @@
             // we need remote access rights for this server, so get it's ip address
             $server_config = $app->getconf->get_server_config($tmp['server_id'], 'server');
             if($server_config['ip_address']!='') {
+                if($this->dataRecord['remote_access'] != 'y') $this->dataRecord['remote_ips'] = '';
                 $this->dataRecord['remote_access'] = 'y';
-                $this->dataRecord['remote_ips'] .= ($this->dataRecord['remote_ips'] != '' ? ',' : '') . $server_config['ip_address'];
+                if(preg_match('/(^|,)' . preg_quote($server_config['ip_address'], '/') . '(,|$)/', $this->dataRecord['remote_ips']) == false) {
+                    $this->dataRecord['remote_ips'] .= ($this->dataRecord['remote_ips'] != '' ? ',' : '') . $server_config['ip_address'];
+                }
             }
         }
         
@@ -261,16 +251,13 @@
 		
 		//* Database username and database name shall not be empty
 		if($this->dataRecord['database_name'] == '') $app->tform->errorMessage .= $app->tform->wordbook["database_name_error_empty"].'<br />';
-		if($this->dataRecord['database_user'] == '') $app->tform->errorMessage .= $app->tform->wordbook["database_user_error_empty"].'<br />';
 
 		//* Get the database name and database user prefix
 		$app->uses('getconf');
 		$global_config = $app->getconf->get_global_config('sites');
 		$dbname_prefix = replacePrefix($global_config['dbname_prefix'], $this->dataRecord);
-		$dbuser_prefix = replacePrefix($global_config['dbuser_prefix'], $this->dataRecord);
 		
 		if(strlen($dbname_prefix . $this->dataRecord['database_name']) > 64) $app->tform->errorMessage .= str_replace('{db}',$dbname_prefix . $this->dataRecord['database_name'],$app->tform->wordbook["database_name_error_len"]).'<br />';
-		if(strlen($dbuser_prefix . $this->dataRecord['database_user']) > 16) $app->tform->errorMessage .= str_replace('{user}',$dbuser_prefix . $this->dataRecord['database_user'],$app->tform->wordbook["database_user_error_len"]).'<br />';
 		
 		//* Check database name and user against blacklist
 		$dbname_blacklist = array($conf['db_database'],'mysql');
@@ -278,16 +265,10 @@
 			$app->tform->errorMessage .= $app->lng('Database name not allowed.').'<br />';
 		}
 		
-		$dbuser_blacklist = array($conf['db_user'],'mysql','root');
-		if(in_array($dbname_prefix . $this->dataRecord['database_user'],$dbname_blacklist)) {
-			$app->tform->errorMessage .= $app->lng('Database user not allowed.').'<br />';
-		}
-
 		/* restrict the names */
         /* crop user and db names if they are too long -> mysql: user: 16 chars / db: 64 chars */
 		if ($app->tform->errorMessage == ''){
 			$this->dataRecord['database_name'] = substr($dbname_prefix . $this->dataRecord['database_name'], 0, 64);
-			$this->dataRecord['database_user'] = substr($dbuser_prefix . $this->dataRecord['database_user'], 0, 16);
 		}
 		
 		//* Check for duplicates
@@ -300,14 +281,139 @@
             // we need remote access rights for this server, so get it's ip address
             $server_config = $app->getconf->get_server_config($tmp['server_id'], 'server');
             if($server_config['ip_address']!='') {
+                if($this->dataRecord['remote_access'] != 'y') $this->dataRecord['remote_ips'] = '';
                 $this->dataRecord['remote_access'] = 'y';
-                $this->dataRecord['remote_ips'] .= (trim($this->dataRecord['remote_ips']) != '' ? ',' : '') . $server_config['ip_address'];
+                if(preg_match('/(^|,)' . preg_quote($server_config['ip_address'], '/') . '(,|$)/', $this->dataRecord['remote_ips']) == false) {
+                    $this->dataRecord['remote_ips'] .= ($this->dataRecord['remote_ips'] != '' ? ',' : '') . $server_config['ip_address'];
+                }
             }
         }
         
 		parent::onBeforeInsert();
 	}
 
+    function onInsertSave($sql) {
+        global $app, $conf;
+
+		if($this->dataRecord["parent_domain_id"] > 0) {
+			$web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($this->dataRecord["parent_domain_id"]));
+		
+			//* The Database user shall be owned by the same group then the website
+			$sys_groupid = $web['sys_groupid'];
+        } else {
+            $sys_groupid = $this->dataRecord['sys_groupid'];
+        }
+        
+
+        if($this->dataRecord['database_user_id']) {
+            // check if there has already been a database on this server with that user
+            $check = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `server_id` = '" . intval($this->dataRecord['server_id']) . "' AND (`database_user_id` = '" . intval($this->dataRecord['database_user_id']) . "' OR `database_ro_user_id` = '" . intval($this->dataRecord['database_user_id']) . "') AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+            
+            if($check && $check['cnt'] < 1) {
+                // we need to make a datalog insert for the database users that are connected to this database
+                $db_user = $app->db->queryOneRecord("SELECT * FROM `web_database_user` WHERE `database_user_id` = '" . intval($this->dataRecord['database_user_id']) . "' AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+                if($db_user) {
+                    $db_user['server_id'] = $this->dataRecord['server_id'];
+                    $app->db->datalogSave('web_database_user', 'INSERT', 'database_user_id', $db_user['database_user_id'], array(), $db_user);
+                }
+            }
+        }
+
+        if($this->dataRecord['database_ro_user_id']) {
+            // check if there has already been a database on this server with that user
+            $check = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `server_id` = '" . intval($this->dataRecord['server_id']) . "' AND (`database_user_id` = '" . intval($this->dataRecord['database_ro_user_id']) . "' OR `database_ro_user_id` = '" . intval($this->dataRecord['database_ro_user_id']) . "') AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+            
+            if($check && $check['cnt'] < 1) {
+                // we need to make a datalog insert for the database users that are connected to this database
+                $db_user = $app->db->queryOneRecord("SELECT * FROM `web_database_user` WHERE `database_user_id` = '" . intval($this->dataRecord['database_ro_user_id']) . "' AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+                if($db_user) {
+                    $db_user['server_id'] = $this->dataRecord['server_id'];
+                    $app->db->datalogSave('web_database_user', 'INSERT', 'database_user_id', $db_user['database_user_id'], array(), $db_user);
+                }
+            }
+        }
+        
+        $app->db->query($sql);
+        if($app->db->errorMessage != '') die($app->db->errorMessage);
+        $new_id = $app->db->insertID();
+        
+        return $new_id;
+    }
+
+    function onUpdateSave($sql) {
+        global $app;
+        if(!empty($sql) && !$app->tform->isReadonlyTab($app->tform->getCurrentTab(),$this->id)) {
+            $old_record = $app->tform->getDataRecord($this->id);
+            
+            if($this->dataRecord["parent_domain_id"] > 0) {
+                $web = $app->db->queryOneRecord("SELECT * FROM web_domain WHERE domain_id = ".intval($this->dataRecord["parent_domain_id"]));
+            
+                //* The Database user shall be owned by the same group then the website
+                $sys_groupid = $web['sys_groupid'];
+            } else {
+                $sys_groupid = $this->dataRecord['sys_groupid'];
+            }
+            
+            // check if database user has changed
+            if($old_record['database_user_id'] && $old_record['database_user_id'] != $this->dataRecord['database_user_id'] && $old_record['database_user_id'] != $this->dataRecord['database_ro_user_id']) {
+                // check if any database on the server still uses this one
+                $check = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `server_id` = '" . intval($this->dataRecord['server_id']) . "' AND (`database_user_id` = '" . intval($old_record['database_user_id']) . "' OR `database_ro_user_id` = '" . intval($old_record['database_user_id']) . "') AND `sys_groupid` = '" . intval($sys_groupid) . "' AND `database_id` != '" . intval($this->id) . "'");
+                if($check['cnt'] < 1) {
+                    // send a datalog delete
+                    $db_user = $app->db->queryOneRecord("SELECT * FROM `web_database_user` WHERE `database_user_id` = '" . intval($old_record['database_user_id']) . "' AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+                    if($db_user) {
+                        $db_user['server_id'] = $this->dataRecord['server_id'];
+                        $app->db->datalogSave('web_database_user', 'DELETE', 'database_user_id', $db_user['database_user_id'], $db_user, array());
+                    }
+                }
+            }
+            // check if readonly database user has changed
+            if($old_record['database_ro_user_id'] && $old_record['database_ro_user_id'] != $this->dataRecord['database_ro_user_id'] && $old_record['database_ro_user_id'] != $this->dataRecord['database_user_id']) {
+                // check if any database on the server still uses this one
+                $check = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `server_id` = '" . intval($this->dataRecord['server_id']) . "' AND (`database_user_id` = '" . intval($old_record['database_ro_user_id']) . "' OR `database_ro_user_id` = '" . intval($old_record['database_ro_user_id']) . "') AND `sys_groupid` = '" . intval($sys_groupid) . "' AND `database_id` != '" . intval($this->id) . "'");
+                if($check['cnt'] < 1) {
+                    // send a datalog delete
+                    $db_user = $app->db->queryOneRecord("SELECT * FROM `web_database_user` WHERE `database_user_id` = '" . intval($old_record['database_ro_user_id']) . "' AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+                    if($db_user) {
+                        $db_user['server_id'] = $this->dataRecord['server_id'];
+                        $app->db->datalogSave('web_database_user', 'DELETE', 'database_user_id', $db_user['database_user_id'], $db_user, array());
+                    }
+                }
+            }
+            
+            if($this->dataRecord['database_user_id']) {
+                // check if there has already been a database on this server with that user
+                $check = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `server_id` = '" . intval($this->dataRecord['server_id']) . "' AND (`database_user_id` = '" . intval($this->dataRecord['database_user_id']) . "' OR `database_ro_user_id` = '" . intval($this->dataRecord['database_user_id']) . "') AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+                
+                if($check && $check['cnt'] < 1) {
+                    // we need to make a datalog insert for the database users that are connected to this database
+                    $db_user = $app->db->queryOneRecord("SELECT * FROM `web_database_user` WHERE `database_user_id` = '" . intval($this->dataRecord['database_user_id']) . "' AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+                    if($db_user) {
+                        $db_user['server_id'] = $this->dataRecord['server_id'];
+                        $app->db->datalogSave('web_database_user', 'INSERT', 'database_user_id', $db_user['database_user_id'], array(), $db_user);
+                    }
+                }
+            }
+
+            if($this->dataRecord['database_ro_user_id']) {
+                // check if there has already been a database on this server with that user
+                $check = $app->db->queryOneRecord("SELECT COUNT(*) as `cnt` FROM `web_database` WHERE `server_id` = '" . intval($this->dataRecord['server_id']) . "' AND (`database_user_id` = '" . intval($this->dataRecord['database_ro_user_id']) . "' OR `database_ro_user_id` = '" . intval($this->dataRecord['database_ro_user_id']) . "') AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+                
+                if($check && $check['cnt'] < 1) {
+                    // we need to make a datalog insert for the database users that are connected to this database
+                    $db_user = $app->db->queryOneRecord("SELECT * FROM `web_database_user` WHERE `database_user_id` = '" . intval($this->dataRecord['database_ro_user_id']) . "' AND `sys_groupid` = '" . intval($sys_groupid) . "'");
+                    if($db_user) {
+                        $db_user['server_id'] = $this->dataRecord['server_id'];
+                        $app->db->datalogSave('web_database_user', 'INSERT', 'database_user_id', $db_user['database_user_id'], array(), $db_user);
+                    }
+                }
+            }
+
+            $app->db->query($sql);
+            if($app->db->errorMessage != '') die($app->db->errorMessage);
+        }
+    }
+    
 	function onAfterInsert() {
 		global $app, $conf;
 		
diff --git a/interface/web/sites/database_user_del.php b/interface/web/sites/database_user_del.php
new file mode 100644
index 0000000..5ecdde5
--- /dev/null
+++ b/interface/web/sites/database_user_del.php
@@ -0,0 +1,78 @@
+<?php
+
+/*
+Copyright (c) 2008, 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.
+*/
+
+/******************************************
+* Begin Form configuration
+******************************************/
+
+$list_def_file = "list/database_user.list.php";
+$tform_def_file = "form/database_user.tform.php";
+
+/******************************************
+* End Form configuration
+******************************************/
+
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+
+//* Check permissions for module
+$app->auth->check_module_permissions('sites');
+
+$app->uses("tform_actions");
+
+class page_action extends tform_actions {
+	function onBeforeDelete() {
+		global $app; $conf;
+		if($app->tform->checkPerm($this->id,'d') == false) $app->error($app->lng('error_no_delete_permission'));
+        
+        $old_record = $app->tform->getDataRecord($this->id);
+        $app->db->datalogDelete('web_database_user', 'database_user_id', $this->id);
+    }
+    
+    function onAfterDelete() { // this has to be done on AFTER delete, because we need the db user still in the database when the server plugin processes the datalog
+		global $app; $conf;
+		
+		//* Update all records that belog to this user
+        $records = $app->db->queryAllRecords("SELECT database_id FROM web_database WHERE database_user_id = '".intval($this->id)."'");
+        foreach($records as $rec) {
+            $app->db->datalogUpdate('web_database','database_user_id=NULL','database_id', $rec['database_id']);
+            
+        }
+        $records = $app->db->queryAllRecords("SELECT database_id FROM web_database WHERE database_ro_user_id = '".intval($this->id)."'");
+        foreach($records as $rec) {
+            $app->db->datalogUpdate('web_database','database_ro_user_id=NULL','database_id', $rec['database_id']);
+        }
+	}
+}
+
+$page = new page_action;
+$page->onDelete();
+
+?>
\ No newline at end of file
diff --git a/interface/web/sites/database_user_edit.php b/interface/web/sites/database_user_edit.php
new file mode 100644
index 0000000..65bf255
--- /dev/null
+++ b/interface/web/sites/database_user_edit.php
@@ -0,0 +1,212 @@
+<?php
+/*
+Copyright (c) 2008, 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.
+*/
+
+
+/******************************************
+* Begin Form configuration
+******************************************/
+
+$tform_def_file = "form/database_user.tform.php";
+
+/******************************************
+* End Form configuration
+******************************************/
+
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+require_once('tools.inc.php');
+
+//* Check permissions for module
+$app->auth->check_module_permissions('sites');
+
+// Loading classes
+$app->uses('tpl,tform,tform_actions');
+$app->load('tform_actions');
+
+class page_action extends tform_actions {
+
+	function onShowEnd() {
+		global $app, $conf, $interfaceConf;
+
+		/*
+		 * If the names are restricted -> remove the restriction, so that the
+		 * data can be edited
+		 */
+		
+		//* Get the database user prefix
+		$app->uses('getconf');
+		$global_config = $app->getconf->get_global_config('sites');
+		$dbuser_prefix = replacePrefix($global_config['dbuser_prefix'], $this->dataRecord);
+		
+        if ($_SESSION["s"]["user"]["typ"] != 'admin' && $app->auth->has_clients($_SESSION['s']['user']['userid'])) {
+			// Get the limits of the client
+			$client_group_id = $_SESSION["s"]["user"]["default_group"];
+			$client = $app->db->queryOneRecord("SELECT client.contactname, client.name, client.client_id FROM sys_group, client WHERE sys_group.client_id = client.client_id and sys_group.groupid = $client_group_id");
+            
+            // Fill the client select field
+            $sql = "SELECT sys_group.groupid, sys_group.name, CONCAT(client.company_name,' :: ',client.contact_name) as contactname FROM sys_group, client WHERE sys_group.client_id = client.client_id AND client.parent_client_id = ".$client['client_id']." ORDER BY sys_group.name";
+            $records = $app->db->queryAllRecords($sql);
+            $tmp = $app->db->queryOneRecord("SELECT groupid FROM sys_group WHERE client_id = ".$client['client_id']);
+            $client_select = '<option value="'.$tmp['groupid'].'">'.$client['name'].' :: '.$client['contactname'].'</option>';
+            //$tmp_data_record = $app->tform->getDataRecord($this->id);
+            if(is_array($records)) {
+                foreach( $records as $rec) {
+                    $selected = @(is_array($this->dataRecord) && ($rec["groupid"] == $this->dataRecord['client_group_id'] || $rec["groupid"] == $this->dataRecord['sys_groupid']))?'SELECTED':'';
+                    $client_select .= "<option value='$rec[groupid]' $selected>$rec[name] :: $rec[contactname]</option>\r\n";
+                }
+            }
+            $app->tpl->setVar("client_group_id",$client_select);
+        } elseif($_SESSION["s"]["user"]["typ"] == 'admin') {
+			// Fill the client select field
+			$sql = "SELECT sys_group.groupid, sys_group.name, CONCAT(client.company_name,' :: ',client.contact_name) as contactname FROM sys_group, client WHERE sys_group.client_id = client.client_id AND sys_group.client_id > 0 ORDER BY sys_group.name";
+			$clients = $app->db->queryAllRecords($sql);
+			$client_select = "<option value='0'></option>";
+			//$tmp_data_record = $app->tform->getDataRecord($this->id);
+			if(is_array($clients)) {
+				foreach( $clients as $client) {
+					//$selected = @($client["groupid"] == $tmp_data_record["sys_groupid"])?'SELECTED':'';
+					$selected = @(is_array($this->dataRecord) && ($client["groupid"] == $this->dataRecord['client_group_id'] || $client["groupid"] == $this->dataRecord['sys_groupid']))?'SELECTED':'';
+					$client_select .= "<option value='$client[groupid]' $selected>$client[name] :: $client[contactname]</option>\r\n";
+				}
+			}
+			$app->tpl->setVar("client_group_id",$client_select);
+        }
+        
+        
+		if ($this->dataRecord['database_user'] != ""){
+			/* REMOVE the restriction */
+			$app->tpl->setVar("database_user", str_replace($dbuser_prefix , '', $this->dataRecord['database_user']));
+		}
+		
+		if($_SESSION["s"]["user"]["typ"] == 'admin' || $app->auth->has_clients($_SESSION['s']['user']['userid'])) {
+			$app->tpl->setVar("database_user_prefix", $global_config['dbuser_prefix']);
+		} else {
+			$app->tpl->setVar("database_user_prefix", $dbuser_prefix);
+		}
+		
+		parent::onShowEnd();
+	}
+    
+    function onSubmit() {
+        global $app;
+        
+        if($_SESSION['s']['user']['typ'] != 'admin' && !$app->auth->has_clients($_SESSION['s']['user']['userid'])) unset($this->dataRecord["client_group_id"]);
+        
+        parent::onSubmit();
+    }
+
+	function onBeforeUpdate() {
+		global $app, $conf, $interfaceConf;
+
+		//* Get the database user prefix
+		$app->uses('getconf');
+		$global_config = $app->getconf->get_global_config('sites');
+		$dbuser_prefix = replacePrefix($global_config['dbuser_prefix'], $this->dataRecord);
+
+		//* Database username shall not be empty
+		if($this->dataRecord['database_user'] == '') $app->tform->errorMessage .= $app->tform->wordbook["database_user_error_empty"].'<br />';
+
+		if(strlen($dbuser_prefix . $this->dataRecord['database_user']) > 16) $app->tform->errorMessage .= str_replace('{user}',$dbuser_prefix . $this->dataRecord['database_user'],$app->tform->wordbook["database_user_error_len"]).'<br />';
+		
+		//* Check database user against blacklist
+		$dbuser_blacklist = array($conf['db_user'],'mysql','root');
+		if(in_array($dbname_prefix . $this->dataRecord['database_user'],$dbname_blacklist)) {
+			$app->tform->errorMessage .= $app->lng('Database user not allowed.').'<br />';
+		}
+		
+		if ($app->tform->errorMessage == ''){
+			/* restrict the names if there is no error */
+            /* crop user and db names if they are too long -> mysql: user: 16 chars / db: 64 chars */
+			$this->dataRecord['database_user'] = substr($dbuser_prefix . $this->dataRecord['database_user'], 0, 16);
+		}
+		
+		parent::onBeforeUpdate();
+	}
+
+	function onBeforeInsert() {
+		global $app, $conf, $interfaceConf;
+		
+		//* Database username shall not be empty
+		if($this->dataRecord['database_user'] == '') $app->tform->errorMessage .= $app->tform->wordbook["database_user_error_empty"].'<br />';
+
+		//* Get the database name and database user prefix
+		$app->uses('getconf');
+		$global_config = $app->getconf->get_global_config('sites');
+		$dbuser_prefix = replacePrefix($global_config['dbuser_prefix'], $this->dataRecord);
+		
+		if(strlen($dbuser_prefix . $this->dataRecord['database_user']) > 16) $app->tform->errorMessage .= str_replace('{user}',$dbuser_prefix . $this->dataRecord['database_user'],$app->tform->wordbook["database_user_error_len"]).'<br />';
+		
+		//* Check database user against blacklist
+		$dbuser_blacklist = array($conf['db_user'],'mysql','root');
+		if(in_array($dbname_prefix . $this->dataRecord['database_user'],$dbname_blacklist)) {
+			$app->tform->errorMessage .= $app->lng('Database user not allowed.').'<br />';
+		}
+
+		/* restrict the names */
+        /* crop user names if they are too long -> mysql: user: 16 chars / db: 64 chars */
+		if ($app->tform->errorMessage == ''){
+			$this->dataRecord['database_user'] = substr($dbuser_prefix . $this->dataRecord['database_user'], 0, 16);
+		}
+		
+		parent::onBeforeInsert();
+	}
+
+	function onAfterInsert() {
+		global $app, $conf;
+		
+		if($_SESSION["s"]["user"]["typ"] == 'admin' && isset($this->dataRecord["client_group_id"])) {
+			$client_group_id = intval($this->dataRecord["client_group_id"]);
+			$app->db->query("UPDATE web_database_user SET sys_groupid = $client_group_id, sys_perm_group = 'riud' WHERE database_user_id = ".$this->id);
+		}
+		if($app->auth->has_clients($_SESSION['s']['user']['userid']) && isset($this->dataRecord["client_group_id"])) {
+			$client_group_id = intval($this->dataRecord["client_group_id"]);
+			$app->db->query("UPDATE web_database_user SET sys_groupid = $client_group_id, sys_perm_group = 'riud' WHERE database_user_id = ".$this->id);
+		}
+	}
+
+	function onAfterUpdate() {
+		global $app, $conf;
+
+		if($_SESSION["s"]["user"]["typ"] == 'admin' && isset($this->dataRecord["client_group_id"])) {
+			$client_group_id = intval($this->dataRecord["client_group_id"]);
+			$app->db->query("UPDATE web_database_user SET sys_groupid = $client_group_id, sys_perm_group = 'riud' WHERE database_user_id = ".$this->id);
+		}
+		if($app->auth->has_clients($_SESSION['s']['user']['userid']) && isset($this->dataRecord["client_group_id"])) {
+			$client_group_id = intval($this->dataRecord["client_group_id"]);
+			$app->db->query("UPDATE web_database_user SET sys_groupid = $client_group_id, sys_perm_group = 'riud' WHERE database_user_id = ".$this->id);
+		}
+
+	}
+
+}
+
+$page = new page_action;
+$page->onLoad();
+
+?>
\ No newline at end of file
diff --git a/interface/web/sites/database_user_list.php b/interface/web/sites/database_user_list.php
new file mode 100644
index 0000000..52a48e4
--- /dev/null
+++ b/interface/web/sites/database_user_list.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+Copyright (c) 2008, 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.
+*/
+
+require_once('../../lib/config.inc.php');
+require_once('../../lib/app.inc.php');
+
+/******************************************
+* Begin Form configuration
+******************************************/
+
+$list_def_file = "list/database_user.list.php";
+
+/******************************************
+* End Form configuration
+******************************************/
+
+//* Check permissions for module
+$app->auth->check_module_permissions('sites');
+
+$app->load('listform_actions');
+
+
+class list_action extends listform_actions {
+	
+	function onShow() {
+		global $app,$conf;
+		
+		parent::onShow();
+	}
+	
+}
+
+$list = new list_action;
+$list->SQLOrderBy = 'ORDER BY database_user';
+$list->onLoad();
+
+
+?>
\ No newline at end of file
diff --git a/interface/web/sites/form/database.tform.php b/interface/web/sites/form/database.tform.php
index b26a1e4..c8a7ac9 100644
--- a/interface/web/sites/form/database.tform.php
+++ b/interface/web/sites/form/database.tform.php
@@ -106,31 +106,27 @@
 			'maxlength'	=> '255',
 			'searchable' => 1
 		),
-		'database_user' => array (
-			'datatype'	=> 'VARCHAR',
-			'formtype'	=> 'TEXT',
-			'validators'	=> array ( 	0 => array (	'type'	=> 'NOTEMPTY',
-														'errmsg'=> 'database_user_error_empty'),
-										1 => array (	'type'	=> 'UNIQUE',
-														'errmsg'=> 'database_user_error_unique'),
-										2 => array (	'type'	=> 'REGEX',
-														'regex' => '/^[a-zA-Z0-9_]{2,64}$/',
-														'errmsg'=> 'database_user_error_regex'),
-									),
+		'database_user_id' => array (
+			'datatype'	=> 'INTEGER',
+			'formtype'	=> 'SELECT',
 			'default'	=> '',
-			'value'		=> '',
-			'width'		=> '30',
-			'maxlength'	=> '255',
-			'searchable' => 2
+			'datasource'	=> array ( 	'type'	=> 'SQL',
+										'querystring' => "SELECT database_user_id,database_user FROM web_database_user WHERE {AUTHSQL} ORDER BY database_user",
+										'keyfield'=> 'database_user_id',
+										'valuefield'=> 'database_user'
+									 ),
+			'value'		=> array('0' => $app->tform->lng('select_dbuser_txt'))
 		),
-		'database_password' => array (
-			'datatype'	=> 'VARCHAR',
-			'formtype'	=> 'PASSWORD',
-			'encryption' => 'MYSQL',
+		'database_ro_user_id' => array (
+			'datatype'	=> 'INTEGER',
+			'formtype'	=> 'SELECT',
 			'default'	=> '',
-			'value'		=> '',
-			'width'		=> '30',
-			'maxlength'	=> '255'
+			'datasource'	=> array ( 	'type'	=> 'SQL',
+										'querystring' => "SELECT database_user_id,database_user FROM web_database_user WHERE {AUTHSQL} ORDER BY database_user",
+										'keyfield'=> 'database_user_id',
+										'valuefield'=> 'database_user'
+									 ),
+			'value'		=> array('0' => $app->tform->lng('no_dbuser_txt'))
 		),
 		'database_charset' => array (
 			'datatype'	=> 'VARCHAR',
diff --git a/interface/web/sites/form/database_user.tform.php b/interface/web/sites/form/database_user.tform.php
new file mode 100644
index 0000000..f8cd38c
--- /dev/null
+++ b/interface/web/sites/form/database_user.tform.php
@@ -0,0 +1,99 @@
+<?php
+
+/*
+	Form Definition
+
+	Tabledefinition
+
+	Datatypes:
+	- INTEGER (Forces the input to Int)
+	- DOUBLE
+	- CURRENCY (Formats the values to currency notation)
+	- VARCHAR (no format check, maxlength: 255)
+	- TEXT (no format check)
+	- DATE (Dateformat, automatic conversion to timestamps)
+
+	Formtype:
+	- TEXT (Textfield)
+	- TEXTAREA (Textarea)
+	- PASSWORD (Password textfield, input is not shown when edited)
+	- SELECT (Select option field)
+	- RADIO
+	- CHECKBOX
+	- CHECKBOXARRAY
+	- FILE
+
+	VALUE:
+	- Wert oder Array
+
+	Hint:
+	The ID field of the database table is not part of the datafield definition.
+	The ID field must be always auto incement (int or bigint).
+	
+	Search:
+	- searchable = 1 or searchable = 2 include the field in the search
+	- searchable = 1: this field will be the title of the search result
+	- searchable = 2: this field will be included in the description of the search result
+
+
+*/
+
+$form["title"] 			= "Database User";
+$form["description"] 	= "";
+$form["name"] 			= "database_user";
+$form["action"]			= "database_user_edit.php";
+$form["db_table"]		= "web_database_user";
+$form["db_table_idx"]	= "database_user_id";
+$form["db_history"]		= "no";
+$form["tab_default"]	= "database_user";
+$form["list_default"]	= "database_user_list.php";
+$form["auth"]			= 'yes'; // yes / no
+
+$form["auth_preset"]["userid"]  = 0; // 0 = id of the user, > 0 id must match with id of current user
+$form["auth_preset"]["groupid"] = 0; // 0 = default groupid of the user, > 0 id must match with groupid of current user
+$form["auth_preset"]["perm_user"] = 'riud'; //r = read, i = insert, u = update, d = delete
+$form["auth_preset"]["perm_group"] = 'riud'; //r = read, i = insert, u = update, d = delete
+$form["auth_preset"]["perm_other"] = ''; //r = read, i = insert, u = update, d = delete
+
+$form["tabs"]['database_user'] = array (
+	'title' 	=> "Database User",
+	'width' 	=> 100,
+	'template' 	=> "templates/database_user_edit.htm",
+	'fields' 	=> array (
+	##################################
+	# Begin Datatable fields
+	##################################
+		'database_user' => array (
+			'datatype'	=> 'VARCHAR',
+			'formtype'	=> 'TEXT',
+			'validators'	=> array ( 	0 => array (	'type'	=> 'NOTEMPTY',
+														'errmsg'=> 'database_user_error_empty'),
+										1 => array (	'type'	=> 'UNIQUE',
+														'errmsg'=> 'database_user_error_unique'),
+										2 => array (	'type'	=> 'REGEX',
+														'regex' => '/^[a-zA-Z0-9_]{2,64}$/',
+														'errmsg'=> 'database_user_error_regex'),
+									),
+			'default'	=> '',
+			'value'		=> '',
+			'width'		=> '30',
+			'maxlength'	=> '255',
+			'searchable' => 2
+		),
+		'database_password' => array (
+			'datatype'	=> 'VARCHAR',
+			'formtype'	=> 'PASSWORD',
+			'encryption' => 'MYSQL',
+			'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 a10771f..36ac619 100644
--- a/interface/web/sites/lib/lang/de_database.lng
+++ b/interface/web/sites/lib/lang/de_database.lng
@@ -3,10 +3,14 @@
 $wb['type_txt'] = 'Typ';
 $wb['database_name_txt'] = 'Datenbankname';
 $wb['database_user_txt'] = 'Datenbank-Benutzer';
+$wb['database_ro_user_txt'] = 'Nur-Lesen Datenbank-Benutzer';
+$wb['optional_txt'] = 'optional';
 $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ür <i>alle</i>)';
+$wb['select_dbuser_txt'] = 'Datenbank User auswählen';
+$wb['no_dbuser_txt'] = 'Keiner';
+$wb['remote_access_txt'] = 'Remotezugriff';
+$wb['remote_ips_txt'] = 'Remotezugriff-IPs (mit Komma trennen, keine Eingabe für <i>alle</i>)';
 $wb['database_remote_error_ips'] = 'Mindestens eine der eingegebenen IP-Adressen ist ungültig.';
 $wb['client_txt'] = 'Kunde';
 $wb['active_txt'] = 'Aktiv';
diff --git a/interface/web/sites/lib/lang/de_database_list.lng b/interface/web/sites/lib/lang/de_database_list.lng
index 62086c9..bae444c 100644
--- a/interface/web/sites/lib/lang/de_database_list.lng
+++ b/interface/web/sites/lib/lang/de_database_list.lng
@@ -1,7 +1,7 @@
 <?php
 $wb['list_head_txt'] = 'Datenbank';
 $wb['active_txt'] = 'Aktiv';
-$wb['remote_access_txt'] = 'Remotezugang';
+$wb['remote_access_txt'] = 'Remotezugriff';
 $wb['server_id_txt'] = 'Server';
 $wb['database_name_txt'] = 'Datenbankname';
 $wb['add_new_record_txt'] = 'Neue Datenbank hinzufügen';
diff --git a/interface/web/sites/lib/lang/de_database_user.lng b/interface/web/sites/lib/lang/de_database_user.lng
new file mode 100644
index 0000000..78884bc
--- /dev/null
+++ b/interface/web/sites/lib/lang/de_database_user.lng
@@ -0,0 +1,23 @@
+<?php
+$wb['database_user_txt'] = 'Datenbank-Benutzer';
+$wb['database_password_txt'] = 'Datenbank-Passwort';
+$wb['client_txt'] = 'Kunde';
+$wb['active_txt'] = 'Aktiv';
+$wb['database_user_error_empty'] = 'Datenbank Benutzer ist leer.';
+$wb['database_user_error_unique'] = 'Es existiert bereits ein Benutzer mit diesem Namen am Server. Um einen eindeutigen Namen zu erhalten können sie z.B. den Domainnamen vor dem Benutzernamen verwenden.';
+$wb['database_user_error_regex'] = 'Ungültiger Benutzername. Der Benutzername darf die Zeichen: a-z, A-Z, 0-9 und den Unterstrich beinhalten. Länge: 2 - 64 Zeichen.';
+$wb['password_strength_txt'] = 'Passwortkomplexität';
+$wb['database_user_error_len'] = 'Datenbank Benutzername - {user} - zu lang. Die max. Datenbank Benutzernamen Länge inkl. Präfix ist 16 Zeichen.';
+$wb['generate_password_txt'] = 'Passwort erzeugen';
+$wb['repeat_password_txt'] = 'Passwort wiederholen';
+$wb['password_mismatch_txt'] = 'Die Passwörter stimmen nicht überein.';
+$wb['password_match_txt'] = 'Die Passwörter stimmen überein.';
+$wb['btn_save_txt'] = 'Speichern';
+$wb['btn_cancel_txt'] = 'Abbrechen';
+$wb['globalsearch_resultslimit_of_txt'] = 'von';
+$wb['globalsearch_resultslimit_results_txt'] = 'Treffern';
+$wb['globalsearch_noresults_text_txt'] = 'Keine Treffer.';
+$wb['globalsearch_noresults_limit_txt'] = '0 Treffer';
+$wb['globalsearch_searchfield_watermark_txt'] = 'Suche';
+$wb['globalsearch_suggestions_text_txt'] = 'Vorschläge';
+?>
diff --git a/interface/web/sites/lib/lang/de_database_user_admin_list.lng b/interface/web/sites/lib/lang/de_database_user_admin_list.lng
new file mode 100644
index 0000000..cac3712
--- /dev/null
+++ b/interface/web/sites/lib/lang/de_database_user_admin_list.lng
@@ -0,0 +1,6 @@
+<?php
+$wb['list_head_txt'] = 'Datenbank User';
+$wb['add_new_record_txt'] = 'Neuen User hinzufügen';
+$wb['database_user_txt'] = 'Datenbank User';
+$wb['sys_groupid_txt'] = 'Kunde';
+?>
diff --git a/interface/web/sites/lib/lang/de_database_user_list.lng b/interface/web/sites/lib/lang/de_database_user_list.lng
new file mode 100644
index 0000000..ac80077
--- /dev/null
+++ b/interface/web/sites/lib/lang/de_database_user_list.lng
@@ -0,0 +1,5 @@
+<?php
+$wb['list_head_txt'] = 'Datenbank User';
+$wb['add_new_record_txt'] = 'Neuen User hinzufügen';
+$wb['database_user_txt'] = 'Datenbank User';
+?>
diff --git a/interface/web/sites/lib/lang/en_database.lng b/interface/web/sites/lib/lang/en_database.lng
index 8a470c7..a494efc 100644
--- a/interface/web/sites/lib/lang/en_database.lng
+++ b/interface/web/sites/lib/lang/en_database.lng
@@ -3,9 +3,13 @@
 $wb["type_txt"] = 'Type';
 $wb["database_name_txt"] = 'Database name';
 $wb["database_user_txt"] = 'Database user';
+$wb['database_ro_user_txt'] = 'Read-only database user';
+$wb['optional_txt'] = 'optional';
 $wb["database_password_txt"] = 'Database password';
 $wb["password_strength_txt"] = 'Password strength';
 $wb["database_charset_txt"] = 'Database charset';
+$wb['select_dbuser_txt'] = 'Select database user';
+$wb['no_dbuser_txt'] = 'None';
 $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.';
diff --git a/interface/web/sites/lib/lang/en_database_user.lng b/interface/web/sites/lib/lang/en_database_user.lng
new file mode 100644
index 0000000..6c4ee7c
--- /dev/null
+++ b/interface/web/sites/lib/lang/en_database_user.lng
@@ -0,0 +1,23 @@
+<?php
+$wb["database_user_txt"] = 'Database user';
+$wb["database_password_txt"] = 'Database password';
+$wb["password_strength_txt"] = 'Password strength';
+$wb["client_txt"] = 'Client';
+$wb["active_txt"] = 'Active';
+$wb["database_user_error_empty"] = 'Database user is empty.';
+$wb["database_user_error_unique"] = 'There is already a database user with this name on the server. To get a unique name, e.g. prepend your domain name to the username.';
+$wb["database_user_error_regex"] = 'Invalid database user name. The username may contain these characters: a-z, A-Z, 0-9 and the underscore. Length: 2 - 64 characters.';
+$wb["database_user_error_len"] = 'Database username - {user} - too long. The max. database username length incl. prefix is 16 chars.';
+$wb["btn_save_txt"] = 'Save';
+$wb["btn_cancel_txt"] = 'Cancel';
+$wb['generate_password_txt'] = 'Generate Password';
+$wb['repeat_password_txt'] = 'Repeat Password';
+$wb['password_mismatch_txt'] = 'The passwords do not match.';
+$wb['password_match_txt'] = 'The passwords do match.';
+$wb['globalsearch_resultslimit_of_txt'] = "of";
+$wb['globalsearch_resultslimit_results_txt'] = "results";
+$wb['globalsearch_noresults_text_txt'] = "No results.";
+$wb['globalsearch_noresults_limit_txt'] = "0 results";
+$wb['globalsearch_searchfield_watermark_txt'] = "Search";
+$wb['globalsearch_suggestions_text_txt'] = "Suggestions";
+?>
diff --git a/interface/web/sites/lib/lang/en_database_user_admin_list.lng b/interface/web/sites/lib/lang/en_database_user_admin_list.lng
new file mode 100644
index 0000000..4c17da1
--- /dev/null
+++ b/interface/web/sites/lib/lang/en_database_user_admin_list.lng
@@ -0,0 +1,6 @@
+<?php
+$wb["list_head_txt"] = 'Database User';
+$wb["database_user_txt"] = 'Database user';
+$wb["add_new_record_txt"] = 'Add new User';
+$wb["sys_groupid_txt"] = 'Client';
+?>
\ No newline at end of file
diff --git a/interface/web/sites/lib/lang/en_database_user_list.lng b/interface/web/sites/lib/lang/en_database_user_list.lng
new file mode 100644
index 0000000..549374b
--- /dev/null
+++ b/interface/web/sites/lib/lang/en_database_user_list.lng
@@ -0,0 +1,5 @@
+<?php
+$wb["list_head_txt"] = 'Database user';
+$wb["database_user_txt"] = 'Database user';
+$wb["add_new_record_txt"] = 'Add new user';
+?>
\ No newline at end of file
diff --git a/interface/web/sites/lib/module.conf.php b/interface/web/sites/lib/module.conf.php
index 1e11450..d79d3f4 100644
--- a/interface/web/sites/lib/module.conf.php
+++ b/interface/web/sites/lib/module.conf.php
@@ -62,15 +62,12 @@
                     'link'	=> 'sites/database_list.php',
                     'html_id'   => 'database_list');
 
-/*
-Database User (for future development)
 
 $items[] = array(   'title'     => "Database User",
                     'target' 	=> 'content',
                     'link'	=> 'sites/database_user_list.php',
                     'html_id'   => 'database_user_list'
 );
-*/
 
 $module["nav"][] = array(   'title' => 'Database',
                             'open'  => 1,
diff --git a/interface/web/sites/lib/remote.conf.php b/interface/web/sites/lib/remote.conf.php
index b2d6ff6..4cdc6fc 100644
--- a/interface/web/sites/lib/remote.conf.php
+++ b/interface/web/sites/lib/remote.conf.php
@@ -1,6 +1,6 @@
 <?php
 $function_list['sites_cron_get,sites_cron_add,sites_cron_update,sites_cron_delete'] = 'Sites cron functions';
-$function_list['sites_database_get,sites_database_add,sites_database_update,sites_database_delete, sites_database_get_all_by_user'] = 'Sites database functions';
+$function_list['sites_database_get,sites_database_add,sites_database_update,sites_database_delete, sites_database_get_all_by_user,sites_database_user_get,sites_database_user_add,sites_database_user_update,sites_database_user_delete, sites_database_user_get_all_by_user'] = 'Sites database functions';
 $function_list['sites_web_folder_get,sites_web_folder_add,sites_web_folder_update,sites_web_folder_delete,sites_web_folder_user_get,sites_web_folder_user_add,sites_web_folder_user_update,sites_web_folder_user_delete'] = 'Sites Protected folder functions';
 $function_list['sites_ftp_user_get,sites_ftp_user_server_get,sites_ftp_user_add,sites_ftp_user_update,sites_ftp_user_delete'] = 'Sites FTP-User functions';
 $function_list['sites_shell_user_get,sites_shell_user_add,sites_shell_user_update,sites_shell_user_delete'] = 'Sites Shell-User functions';
diff --git a/interface/web/sites/list/database.list.php b/interface/web/sites/list/database.list.php
index 9b2111d..6163f37 100644
--- a/interface/web/sites/list/database.list.php
+++ b/interface/web/sites/list/database.list.php
@@ -100,12 +100,17 @@
 							'width'		=> "",
 							'value'		=> "");
 
-$liste["item"][] = array(	'field'		=> "database_user",
-							'datatype'	=> "VARCHAR",
-							'formtype'	=> "TEXT",
-							'op'		=> "like",
-							'prefix'	=> "%",
-							'suffix'	=> "%",
+$liste["item"][] = array(	'field'		=> "database_user_id",
+							'datatype'	=> "INTEGER",
+							'formtype'	=> "SELECT",
+							'op'		=> "=",
+							'prefix'	=> "",
+							'suffix'	=> "",
+							'datasource'	=> array ( 	'type'	=> 'SQL',
+														'querystring' => 'SELECT database_user_id, database_user FROM web_database_user WHERE {AUTHSQL} ORDER BY database_user',
+														'keyfield'=> 'database_user_id',
+														'valuefield'=> 'database_user'
+									 				  ),
 							'width'		=> "",
 							'value'		=> "");
 
diff --git a/interface/web/sites/list/database_user.list.php b/interface/web/sites/list/database_user.list.php
new file mode 100644
index 0000000..67796a8
--- /dev/null
+++ b/interface/web/sites/list/database_user.list.php
@@ -0,0 +1,81 @@
+<?php
+
+/*
+	Datatypes:
+	- INTEGER
+	- DOUBLE
+	- CURRENCY
+	- VARCHAR
+	- TEXT
+	- DATE
+*/
+
+
+
+// Name of the list
+if($_SESSION['s']['user']['typ'] == 'admin') {
+	$liste["name"] 				= "database_user_admin";
+} else {
+	$liste["name"] 				= "database_user";
+}
+
+// Database table
+$liste["table"] 			= "web_database_user";
+
+// Index index field of the database table
+$liste["table_idx"]			= "database_user_id";
+
+// Search Field Prefix
+$liste["search_prefix"] 	= "search_";
+
+// Records per page
+$liste["records_per_page"] 	= "15";
+
+// Script File of the list
+$liste["file"]				= "database_user_list.php";
+
+// Script file of the edit form
+$liste["edit_file"]			= "database_user_edit.php";
+
+// Script File of the delete script
+$liste["delete_file"]		= "database_user_del.php";
+
+// Paging Template
+$liste["paging_tpl"]		= "templates/paging.tpl.htm";
+
+// Enable auth
+$liste["auth"]				= "yes";
+
+
+/*****************************************************
+* Suchfelder
+*****************************************************/
+
+
+if($_SESSION['s']['user']['typ'] == 'admin') {
+$liste["item"][] = array(	'field'		=> "sys_groupid",
+							'datatype'	=> "INTEGER",
+							'formtype'	=> "SELECT",
+							'op'		=> "=",
+							'prefix'	=> "",
+							'suffix'	=> "",
+							'datasource'	=> array ( 	'type'	=> 'SQL',
+														'querystring' => 'SELECT groupid, name FROM sys_group WHERE groupid != 1 ORDER BY name',
+														'keyfield'=> 'groupid',
+														'valuefield'=> 'name'
+									 				  ),
+							'width'		=> "",
+							'value'		=> "");
+}
+
+$liste["item"][] = array(	'field'		=> "database_user",
+							'datatype'	=> "VARCHAR",
+							'formtype'	=> "TEXT",
+							'op'		=> "like",
+							'prefix'	=> "%",
+							'suffix'	=> "%",
+							'width'		=> "",
+							'value'		=> "");
+
+
+?>
\ No newline at end of file
diff --git a/interface/web/sites/templates/database_admin_list.htm b/interface/web/sites/templates/database_admin_list.htm
index b6c90b5..fda3ba7 100644
--- a/interface/web/sites/templates/database_admin_list.htm
+++ b/interface/web/sites/templates/database_admin_list.htm
@@ -30,7 +30,7 @@
                         <td class="tbl_col_remote_access"><select name="search_remote_access">{tmpl_var name='search_remote_access'}</select></td>
                         <td class="tbl_col_sys_groupid"><select name="search_sys_groupid">{tmpl_var name='search_sys_groupid'}</select></td>
                         <td class="tbl_col_server_id"><select name="search_server_id">{tmpl_var name='search_server_id'}</select></td>
-                        <td class="tbl_col_database_user"><input type="text" name="search_database_user" value="{tmpl_var name='search_database_user'}" /></td>
+                        <td class="tbl_col_database_user"><select name="search_database_user_id">{tmpl_var name='search_database_user_id'}</select></td>
                         <td class="tbl_col_database_name"><input type="text" name="search_database_name" value="{tmpl_var name='search_database_name'}" /></td>
                         <td class="tbl_col_buttons"><div class="buttons"><button type="button" class="icons16 icoFilter" name="Filter" id="Filter" value="{tmpl_var name="filter_txt"}" onClick="submitForm('pageForm','sites/database_list.php');"><span>{tmpl_var name="filter_txt"}</span></button></div></td>
                     </tr>
@@ -42,7 +42,7 @@
                             <td class="tbl_col_remote_access"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="remote_access"}</a></td>
                             <td class="tbl_col_sys_groupid"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="sys_groupid"}</a></td>
                             <td class="tbl_col_server_id"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="server_id"}</a></td>
-                            <td class="tbl_col_database_user"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="database_user"}</a></td>
+                            <td class="tbl_col_database_user"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="database_user_id"}</a></td>
                             <td class="tbl_col_database_name"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="database_name"}</a></td>
                             <td class="tbl_col_buttons">
                                 <div class="buttons icons16">
diff --git a/interface/web/sites/templates/database_edit.htm b/interface/web/sites/templates/database_edit.htm
index b8b4b3f..4207a78 100644
--- a/interface/web/sites/templates/database_edit.htm
+++ b/interface/web/sites/templates/database_edit.htm
@@ -44,25 +44,17 @@
                 </tmpl_if>
             </div>
             <div class="ctrlHolder">
-                <label for="database_user">{tmpl_var name='database_user_txt'}</label>
-                <p class="prefix">{tmpl_var name='database_user_prefix'}</p>
-                <input name="database_user" id="database_user" value="{tmpl_var name='database_user'}" size="30" maxlength="255" type="text" class="textInput formLengthHalf" />
+                <label for="database_user_id">{tmpl_var name='database_user_txt'}</label>
+                <select name="database_user_id" id="database_user_id" class="selectInput">
+                    {tmpl_var name='database_user_id'}
+                </select>
             </div>
             <div class="ctrlHolder">
-                <label for="database_password">{tmpl_var name='database_password_txt'}</label>
-                <input name="database_password" id="database_password" value="{tmpl_var name='database_password'}" size="30" maxlength="255" type="password" class="textInput formLengthHalf" onkeyup="pass_check(this.value);checkPassMatch('database_password','repeat_password');" />&nbsp;<a href="javascript:void(0);" onClick="generatePassword('database_password','repeat_password');">{tmpl_var name='generate_password_txt'}</a>
+                <label for="database_ro_user_id">{tmpl_var name='database_ro_user_txt'}</label>
+                <select name="database_ro_user_id" id="database_ro_user_id" class="selectInput">
+                    {tmpl_var name='database_ro_user_id'}
+                </select>&nbsp;{tmpl_var name='optional_txt'}
             </div>
-            <div class="ctrlHolder">
-                <p class="label">{tmpl_var name='password_strength_txt'}</p>
-                <div id="passBar"></div>
-                <p class="formHint"><span id="passText">&nbsp;</span></p>
-            </div>
-            <div class="ctrlHolder">
-                <label for="repeat_password">{tmpl_var name='repeat_password_txt'}</label>
-                <input name="repeat_password" id="repeat_password" value="" size="15" maxlength="100" type="password" class="textInput" style="width:100px;"  onkeyup="checkPassMatch('database_password','repeat_password');" />
-            </div>
-            <div id="confirmpasswordError" style="display:none;" class="confirmpassworderror">{tmpl_var name='password_mismatch_txt'}</div>
-            <div id="confirmpasswordOK" style="display:none;" class="confirmpasswordok">{tmpl_var name='password_match_txt'}</div>
             <div class="ctrlHolder">
                 <tmpl_if name="edit_disabled">
                     <label for="database_charset_disabled">{tmpl_var name='database_charset_txt'}</label>
diff --git a/interface/web/sites/templates/database_list.htm b/interface/web/sites/templates/database_list.htm
index d3b9ac5..7b49e87 100644
--- a/interface/web/sites/templates/database_list.htm
+++ b/interface/web/sites/templates/database_list.htm
@@ -21,7 +21,7 @@
                         <th class="tbl_col_active" scope="col"><tmpl_var name="active_txt"></th>
                         <th class="tbl_col_remote_access" scope="col"><tmpl_var name="remote_access_txt"></th>
                         <th class="tbl_col_server_id" scope="col"><tmpl_var name="server_id_txt"></th>
-                        <th class="tbl_col_database_name" scope="col"><tmpl_var name="database_user_txt"></th>
+                        <th class="tbl_col_database_user" scope="col"><tmpl_var name="database_user_txt"></th>
                         <th class="tbl_col_database_name" scope="col"><tmpl_var name="database_name_txt"></th>
                         <th class="tbl_col_limit" scope="col">{tmpl_var name='search_limit'}</th>
                     </tr>
@@ -29,7 +29,7 @@
                         <td class="tbl_col_active"><select name="search_active">{tmpl_var name='search_active'}</select></td>
                         <td class="tbl_col_remote_access"><select name="search_remote_access">{tmpl_var name='search_remote_access'}</select></td>
                         <td class="tbl_col_server_id"><select name="search_server_id">{tmpl_var name='search_server_id'}</select></td>
-                        <td class="tbl_col_database_user"><input type="text" name="search_database_user" value="{tmpl_var name='search_database_user'}"/></td>
+                        <td class="tbl_col_database_user"><select name="search_database_user_id">{tmpl_var name='search_database_user_id'}</select></td>
                         <td class="tbl_col_database_name"><input type="text" name="search_database_name" value="{tmpl_var name='search_database_name'}"/></td>
                         <td class="tbl_col_buttons"><div class="buttons"><button type="button" class="icons16 icoFilter" name="Filter" id="Filter" value="{tmpl_var name="filter_txt"}" onClick="submitForm('pageForm','sites/database_list.php');"><span>{tmpl_var name="filter_txt"}</span></button></div></td>
                     </tr>
@@ -40,7 +40,7 @@
                             <td class="tbl_col_active"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="active"}</a></td>
                             <td class="tbl_col_remote_access"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="remote_access"}</a></td>
                             <td class="tbl_col_server_id"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="server_id"}</a></td>
-                            <td class="tbl_col_database_user"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="database_user"}</a></td>
+                            <td class="tbl_col_database_user"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="database_user_id"}</a></td>
                             <td class="tbl_col_database_name"><a href="#" onClick="loadContent('sites/database_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="database_name"}</a></td>
                             <td class="tbl_col_buttons">
                                 <div class="buttons icons16">    
diff --git a/interface/web/sites/templates/database_user_admin_list.htm b/interface/web/sites/templates/database_user_admin_list.htm
new file mode 100644
index 0000000..c1bee67
--- /dev/null
+++ b/interface/web/sites/templates/database_user_admin_list.htm
@@ -0,0 +1,50 @@
+<h2><tmpl_var name="list_head_txt"></h2>
+
+<div class="panel panel_list_database_user_admin">
+
+    <div class="pnl_toolsarea">
+        <fieldset><legend>{tmpl_var name="toolsarea_head_txt"}</legend>
+            <div class="buttons">
+                <button class="iconstxt icoAdd" type="button" onClick="loadContent('sites/database_user_edit.php');">
+                    <span>{tmpl_var name="add_new_record_txt"}</span>
+                </button>
+            </div>
+        </fieldset>
+    </div>
+
+    <div class="pnl_listarea">
+        <fieldset><legend><tmpl_var name="list_head_txt"></legend>
+            <table class="list">
+                <thead>
+                    <tr>
+                        <th class="tbl_col_database_user" scope="col"><tmpl_var name="database_user_txt"></th>
+                        <th class="tbl_col_limit" scope="col">{tmpl_var name='search_limit'}</th>
+                    </tr>
+                    <tr>
+                        <td class="tbl_col_database_user"><input type="text" name="search_database_user" value="{tmpl_var name='search_database_user'}" /></td>
+                        <td class="tbl_col_buttons"><div class="buttons"><button type="button" class="icons16 icoFilter" name="Filter" id="Filter" value="{tmpl_var name="filter_txt"}" onClick="submitForm('pageForm','sites/database_user_list.php');"><span>{tmpl_var name="filter_txt"}</span></button></div></td>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tmpl_loop name="records">
+                        <tr class="tbl_row_<tmpl_if name='__EVEN__'}even<tmpl_else>uneven</tmpl_if>">
+                            <td class="tbl_col_database_user"><a href="#" onClick="loadContent('sites/database_user_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="database_user"}</a></td>
+                            <td class="tbl_col_buttons">
+                                <div class="buttons icons16">
+                                    <a class="icons16 icoEdit" href="javascript: loadContent('sites/database_user_edit.php?id={tmpl_var name='id'}');"><span>{tmpl_var name='edit_txt'}</span></a>
+                                    <a class="icons16 icoDelete" href="javascript: del_record('sites/database_user_del.php?id={tmpl_var name='id'}&phpsessid={tmpl_var name='phpsessid'}','{tmpl_var name='delete_confirmation'}');"><span>{tmpl_var name='delete_txt'}</span></a>
+                                </div>
+                            </td>
+                        </tr>
+                    </tmpl_loop>
+                </tbody>
+                <tfoot>
+                    <tr>
+                        <td class="tbl_footer tbl_paging" colspan="2"><tmpl_var name="paging"></td>
+                    </tr>
+                </tfoot>
+            </table>
+        </fieldset>
+    </div>
+
+</div>
\ No newline at end of file
diff --git a/interface/web/sites/templates/database_user_edit.htm b/interface/web/sites/templates/database_user_edit.htm
new file mode 100644
index 0000000..40e8c96
--- /dev/null
+++ b/interface/web/sites/templates/database_user_edit.htm
@@ -0,0 +1,54 @@
+<h2><tmpl_var name="list_head_txt"></h2>
+<p><tmpl_var name="list_desc_txt"></p>
+
+<div class="panel panel_database_user">
+
+    <div class="pnl_formsarea">
+        <fieldset class="inlineLabels">
+            <tmpl_if name="is_admin">
+                <div class="ctrlHolder">
+                    <label for="client_group_id">{tmpl_var name='client_txt'}</label>
+                    <select name="client_group_id" id="client_group_id" class="selectInput">
+                        {tmpl_var name='client_group_id'}
+                    </select>
+                </div>
+            </tmpl_if>
+            <tmpl_if name="is_reseller">
+                <div class="ctrlHolder">
+                    <label for="client_group_id">{tmpl_var name='client_txt'}</label>
+                    <select name="client_group_id" id="client_group_id" class="selectInput">
+                        {tmpl_var name='client_group_id'}
+                    </select>
+                </div>
+            </tmpl_if>
+            <div class="ctrlHolder">
+                <label for="database_user">{tmpl_var name='database_user_txt'}</label>
+                <p class="prefix">{tmpl_var name='database_user_prefix'}</p>
+                <input name="database_user" id="database_user" value="{tmpl_var name='database_user'}" size="30" maxlength="255" type="text" class="textInput formLengthHalf" />
+            </div>
+            <div class="ctrlHolder">
+                <label for="database_password">{tmpl_var name='database_password_txt'}</label>
+                <input name="database_password" id="database_password" value="{tmpl_var name='database_password'}" size="30" maxlength="255" type="password" class="textInput formLengthHalf" onkeyup="pass_check(this.value);checkPassMatch('database_password','repeat_password');" />&nbsp;<a href="javascript:void(0);" onClick="generatePassword('database_password','repeat_password');">{tmpl_var name='generate_password_txt'}</a>
+            </div>
+            <div class="ctrlHolder">
+                <p class="label">{tmpl_var name='password_strength_txt'}</p>
+                <div id="passBar"></div>
+                <p class="formHint"><span id="passText">&nbsp;</span></p>
+            </div>
+            <div class="ctrlHolder">
+                <label for="repeat_password">{tmpl_var name='repeat_password_txt'}</label>
+                <input name="repeat_password" id="repeat_password" value="" size="15" maxlength="100" type="password" class="textInput" style="width:100px;"  onkeyup="checkPassMatch('database_password','repeat_password');" />
+            </div>
+            <div id="confirmpasswordError" style="display:none;" class="confirmpassworderror">{tmpl_var name='password_mismatch_txt'}</div>
+            <div id="confirmpasswordOK" style="display:none;" class="confirmpasswordok">{tmpl_var name='password_match_txt'}</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/database_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/database_user_list.php');"><span>{tmpl_var name='btn_cancel_txt'}</span></button>
+        </div>
+    </div>
+
+</div>
\ No newline at end of file
diff --git a/interface/web/sites/templates/database_user_list.htm b/interface/web/sites/templates/database_user_list.htm
new file mode 100644
index 0000000..0a0ea1a
--- /dev/null
+++ b/interface/web/sites/templates/database_user_list.htm
@@ -0,0 +1,51 @@
+<h2><tmpl_var name="list_head_txt"></h2>
+<p><tmpl_var name="list_desc_txt"></p>
+
+<div class="panel panel_list_database">
+
+    <div class="pnl_toolsarea">
+        <fieldset><legend>{tmpl_var name="toolsarea_head_txt"}</legend>
+            <div class="buttons">
+                <button class="iconstxt icoAdd" type="button" onClick="loadContent('sites/database_user_edit.php');">
+                    <span>{tmpl_var name="add_new_record_txt"}</span>
+                </button>
+            </div>
+        </fieldset>
+    </div>
+
+    <div class="pnl_listarea">
+        <fieldset><legend><tmpl_var name="list_head_txt"></legend>
+            <table class="list">
+                <thead>
+                    <tr>
+                        <th class="tbl_col_database_user" scope="col"><tmpl_var name="database_user_txt"></th>
+                        <th class="tbl_col_limit" scope="col">{tmpl_var name='search_limit'}</th>
+                    </tr>
+                    <tr>
+                        <td class="tbl_col_database_user"><input type="text" name="search_database_user" value="{tmpl_var name='search_database_user'}"/></td>
+                        <td class="tbl_col_buttons"><div class="buttons"><button type="button" class="icons16 icoFilter" name="Filter" id="Filter" value="{tmpl_var name="filter_txt"}" onClick="submitForm('pageForm','sites/database_user_list.php');"><span>{tmpl_var name="filter_txt"}</span></button></div></td>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tmpl_loop name="records">
+                        <tr class="tbl_row_<tmpl_if name='__EVEN__'}even<tmpl_else>uneven</tmpl_if>">
+                            <td class="tbl_col_database_user"><a href="#" onClick="loadContent('sites/database_user_edit.php?id={tmpl_var name='id'}');">{tmpl_var name="database_user"}</a></td>
+                            <td class="tbl_col_buttons">
+                                <div class="buttons icons16">    
+                                    <a class="icons16 icoEdit" href="javascript: loadContent('sites/database_user_edit.php?id={tmpl_var name='id'}');"><span>{tmpl_var name='edit_txt'}</span></a>
+                                    <a class="icons16 icoDelete" href="javascript: del_record('sites/database_user_del.php?id={tmpl_var name='id'}&phpsessid={tmpl_var name='phpsessid'}','{tmpl_var name='delete_confirmation'}');"><span>{tmpl_var name='delete_txt'}</span></a>
+                                </div>
+                            </td>
+                        </tr>
+                    </tmpl_loop>
+                </tbody>
+                <tfoot>
+                    <tr>
+                        <td class="tbl_footer tbl_paging" colspan="2"><tmpl_var name="paging"></td>
+                    </tr>
+                </tfoot>
+            </table>
+        </fieldset>
+    </div>
+
+</div>
\ No newline at end of file
diff --git a/server/mods-available/database_module.inc.php b/server/mods-available/database_module.inc.php
index 199a6c7..c3a99b6 100644
--- a/server/mods-available/database_module.inc.php
+++ b/server/mods-available/database_module.inc.php
@@ -34,7 +34,10 @@
 	var $class_name = 'database_module';
 	var $actions_available = array(	'database_insert',
 									'database_update',
-									'database_delete'
+									'database_delete',
+									'database_user_insert',
+									'database_user_update',
+									'database_user_delete'
 									);
 	
 	//* This function is called during ispconfig installation to determine
@@ -75,7 +78,7 @@
 		*/
 		
 		$app->modules->registerTableHook('web_database','database_module','process');
-		//$app->modules->registerTableHook('web_database_user','database_module','process');
+		$app->modules->registerTableHook('web_database_user','database_module','process');
 		
 		// Register service
 		//$app->services->registerService('httpd','web_module','restartHttpd');
@@ -96,13 +99,13 @@
 				if($action == 'u') $app->plugins->raiseEvent('database_update',$data);
 				if($action == 'd') $app->plugins->raiseEvent('database_delete',$data);
 			break;
-			/*
+			
 			case 'web_database_user':
 				if($action == 'i') $app->plugins->raiseEvent('database_user_insert',$data);
 				if($action == 'u') $app->plugins->raiseEvent('database_user_update',$data);
 				if($action == 'd') $app->plugins->raiseEvent('database_user_delete',$data);
 			break;
-			*/
+			
 		} // end switch
 	} // end function
 
diff --git a/server/plugins-available/mysql_clientdb_plugin.inc.php b/server/plugins-available/mysql_clientdb_plugin.inc.php
index df8749a..98efd8c 100644
--- a/server/plugins-available/mysql_clientdb_plugin.inc.php
+++ b/server/plugins-available/mysql_clientdb_plugin.inc.php
@@ -64,14 +64,14 @@
 		$app->plugins->registerEvent('database_delete',$this->plugin_name,'db_delete');
 		
 		//* Database users
-		//$app->plugins->registerEvent('database_user_insert',$this->plugin_name,'db_user_insert');
-		//$app->plugins->registerEvent('database_user_update',$this->plugin_name,'db_user_update');
-		//$app->plugins->registerEvent('database_user_delete',$this->plugin_name,'db_user_delete');
+		$app->plugins->registerEvent('database_user_insert',$this->plugin_name,'db_user_insert');
+		$app->plugins->registerEvent('database_user_update',$this->plugin_name,'db_user_update');
+		$app->plugins->registerEvent('database_user_delete',$this->plugin_name,'db_user_delete');
 		
 		
 	}
 	
-  function process_host_list($action, $database_name, $database_user, $database_password, $host_list, $link, $database_rename_user = '') {
+  function process_host_list($action, $database_name, $database_user, $database_password, $host_list, $link, $database_rename_user = '', $user_read_only = false) {
       global $app;
       
       $action = strtoupper($action);
@@ -105,9 +105,9 @@
           if($valid == false) continue;
           
           if($action == 'GRANT') {
-              if(!$link->query("GRANT ALL ON ".$link->escape_string($database_name).".* TO '".$link->escape_string($database_user)."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($database_password)."';")) $success = false;
+              if(!$link->query("GRANT " . ($user_read_only ? "SELECT" : "ALL") . " ON ".$link->escape_string($database_name).".* TO '".$link->escape_string($database_user)."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($database_password)."';")) $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);
+              if(!$link->query("REVOKE ALL PRIVILEGES ON ".$link->escape_string($database_name).".* FROM '".$link->escape_string($database_user)."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($database_password)."';")) $success = false;
           } elseif($action == 'DROP') {
               if(!$link->query("DROP USER '".$link->escape_string($database_user)."'@'$db_host';")) $success = false;
           } elseif($action == 'RENAME') {
@@ -129,11 +129,6 @@
 				return;
 			}
 			
-			if($data['new']['database_user'] == 'root') {
-				$app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
-				return;
-			}
-		
 			//* Connect to the database
 			$link = new mysqli($clientdb_host, $clientdb_user, $clientdb_password);
 			if ($link->connect_error) {
@@ -158,13 +153,26 @@
 			// Create the database user if database is active
 			if($data['new']['active'] == 'y') {
 				
-				if($data['new']['remote_access'] == 'y') {
-          $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';
-				$link->query("GRANT ALL ON `".str_replace(array('_','%'),array('\\_','\\%'),$link->escape_string($data['new']['database_name']))."`.* TO '".$link->escape_string($data['new']['database_user'])."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($data['new']['database_password'])."';");
-
+                // get the users for this database
+                $db_user = $app->db->queryOneRecord("SELECT `database_user`, `database_password` FROM `web_database_user` WHERE `database_user_id` = '" . intval($data['new']['database_user_id']) . "'");
+                
+                $db_ro_user = $app->db->queryOneRecord("SELECT `database_user`, `database_password` FROM `web_database_user` WHERE `database_user_id` = '" . intval($data['new']['database_ro_user_id']) . "'");
+                
+                $host_list = '';
+                if($data['new']['remote_access'] == 'y') {
+                    $host_list = $data['new']['remote_ips'];
+                }
+                if($host_list != '') $host_list .= ',';
+                $host_list .= 'localhost';
+                
+                if($db_user) {
+                    if($db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else $this->process_host_list('GRANT', $data['new']['database_name'], $db_user['database_user'], $db_user['database_password'], $host_list, $link);
+                }
+                if($db_ro_user && $data['new']['database_user_id'] != $data['new']['database_ro_user_id']) {
+                    if($db_ro_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else $this->process_host_list('GRANT', $data['new']['database_name'], $db_ro_user['database_user'], $db_ro_user['database_password'], $host_list, $link, '', true);
+                }
 				
 			}
 			
@@ -182,11 +190,6 @@
 				return;
 			}
 			
-			if($data['new']['database_user'] == 'root') {
-				$app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
-				return;
-			}
-			
 			//* Connect to the database
 			$link = new mysqli($clientdb_host, $clientdb_user, $clientdb_password);
 			if ($link->connect_error) {
@@ -194,41 +197,66 @@
 				return;
 			}
 			
-			// Create the database user if database was disabled before
+            // get the users for this database
+            $db_user = $app->db->queryOneRecord("SELECT `database_user`, `database_password` FROM `web_database_user` WHERE `database_user_id` = '" . intval($data['new']['database_user_id']) . "'");
+            
+            $db_ro_user = $app->db->queryOneRecord("SELECT `database_user`, `database_password` FROM `web_database_user` WHERE `database_user_id` = '" . intval($data['new']['database_ro_user_id']) . "'");
+            
+            $host_list = '';
+            if($data['new']['remote_access'] == 'y') {
+                $host_list = $data['new']['remote_ips'];
+            }
+            if($host_list != '') $host_list .= ',';
+            $host_list .= 'localhost';
+            
+            // Create the database user if database was disabled before
 			if($data['new']['active'] == 'y' && $data['old']['active'] == 'n') {
-				
-				if($data['new']['remote_access'] == 'y') {
-				  $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';
-				$link->query("GRANT ALL ON `".str_replace(array('_','%'),array('\\_','\\%'),$link->escape_string($data['new']['database_name']))."`.* TO '".$link->escape_string($data['new']['database_user'])."'@'$db_host' IDENTIFIED BY PASSWORD '".$link->escape_string($data['new']['database_password'])."';");
-				
-				// 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);
-				//echo "GRANT ALL ON ".mysql_real_escape_string($data["new"]["database_name"]).".* TO '".mysql_real_escape_string($data["new"]["database_user"])."'@'$db_host' IDENTIFIED BY '".mysql_real_escape_string($data["new"]["database_password"])."';";
+                if($db_user) {
+                    if($db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else $this->process_host_list('GRANT', $data['new']['database_name'], $db_user['database_user'], $db_user['database_password'], $host_list, $link);
+                }
+                if($db_ro_user && $data['new']['database_user_id'] != $data['new']['database_ro_user_id']) {
+                    if($db_ro_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else $this->process_host_list('GRANT', $data['new']['database_name'], $db_ro_user['database_user'], $db_ro_user['database_password'], $host_list, $link, '', true);
+                }
+			} else if($data['new']['active'] == 'n' && $data['old']['active'] == 'y') { // revoke database user, if inactive
+                if($db_user) {
+                    if($db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else $this->process_host_list('REVOKE', $data['new']['database_name'], $db_user['database_user'], $db_user['database_password'], $host_list, $link);
+                }
+                if($db_ro_user && $data['new']['database_user_id'] != $data['new']['database_ro_user_id']) {
+                    if($db_ro_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else $this->process_host_list('REVOKE', $data['new']['database_name'], $db_ro_user['database_user'], $db_ro_user['database_password'], $host_list, $link);
+                }
 			}
-			
-			// Remove database user, if inactive
-			if($data['new']['active'] == 'n' && $data['old']['active'] == 'y') {
-				
-				if($data['old']['remote_access'] == 'y') {
-          $this->process_host_list('DROP', '', $data['old']['database_user'], '', $data['old']['remote_ips'], $link);
-				}
-				
-				$db_host = 'localhost';
-				$link->query("DROP USER '".$link->escape_string($data['old']['database_user'])."'@'$db_host';");
-				//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);
-			}
-			
-			//* Rename User
-			if($data['new']['database_user'] != $data['old']['database_user']) {
-				$db_host = 'localhost';
-				$link->query("RENAME USER '".$link->escape_string($data['old']['database_user'])."'@'$db_host' TO '".$link->escape_string($data['new']['database_user'])."'@'$db_host'");
-				if($data['old']['remote_access'] == 'y') {
-					$this->process_host_list('RENAME', '', $data['old']['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);
-			}
+            
+            //* selected Users have changed
+            if($data['new']['database_user_id'] != $data['old']['database_user_id']) {
+                if($data['old']['database_user_id'] && $data['old']['database_user_id'] != $data['new']['database_ro_user_id']) {
+                    $old_db_user = $app->db->queryOneRecord("SELECT `database_user`, `database_password` FROM `web_database_user` WHERE `database_user_id` = '" . intval($data['old']['database_user_id']) . "'");
+                    if($old_db_user) {
+                        if($old_db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                        else $this->process_host_list('REVOKE', $data['new']['database_name'], $old_db_user['database_user'], $old_db_user['database_password'], $host_list, $link);
+                    }
+                }
+                if($db_user) {
+                    if($db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else $this->process_host_list('GRANT', $data['new']['database_name'], $db_user['database_user'], $db_user['database_password'], $host_list, $link);
+                }
+            }
+            if($data['new']['database_ro_user_id'] != $data['old']['database_ro_user_id']) {
+                if($data['old']['database_ro_user_id'] && $data['old']['database_ro_user_id'] != $data['new']['database_user_id']) {
+                    $old_db_user = $app->db->queryOneRecord("SELECT `database_user`, `database_password` FROM `web_database_user` WHERE `database_user_id` = '" . intval($data['old']['database_ro_user_id']) . "'");
+                    if($old_db_user) {
+                        if($old_db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                        else $this->process_host_list('REVOKE', $data['new']['database_name'], $old_db_user['database_user'], $old_db_user['database_password'], $host_list, $link);
+                    }
+                }
+                if($db_ro_user && $data['new']['database_user_id'] != $data['new']['database_ro_user_id']) {
+                    if($db_ro_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else $this->process_host_list('GRANT', $data['new']['database_name'], $db_ro_user['database_user'], $db_ro_user['database_password'], $host_list, $link, '', true);
+                }
+            }
 			
 			//* Remote access option has changed.
 			if($data['new']['remote_access'] != $data['old']['remote_access']) {
@@ -238,27 +266,43 @@
 				
 				//* set new priveliges
 				if($data['new']['remote_access'] == 'y') { 		
-					$this->process_host_list('GRANT', $data['new']['database_name'], $data['new']['database_user'], $data['new']['database_password'], $data['new']['remote_ips'], $link);
+                    if($db_user) {
+                        if($db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                        else $this->process_host_list('GRANT', $data['new']['database_name'], $db_user['database_user'], $db_user['database_password'], $data['new']['remote_ips'], $link);
+                    }
+                    if($db_ro_user && $data['new']['database_user_id'] != $data['new']['database_ro_user_id']) {
+                        if($db_ro_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                        else $this->process_host_list('GRANT', $data['new']['database_name'], $db_ro_user['database_user'], $db_ro_user['database_password'], $data['new']['remote_ips'], $link, '', true);
+                    }
 				} else {
-					$this->process_host_list('DROP', '', $data['old']['database_user'], '', $data['old']['remote_ips'], $link);
+                    if($db_user) {
+                        if($db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                        else $this->process_host_list('REVOKE', $data['new']['database_name'], $db_user['database_user'], $db_user['database_password'], $data['new']['remote_ips'], $link);
+                    }
+                    if($db_ro_user && $data['new']['database_user_id'] != $data['new']['database_ro_user_id']) {
+                        if($db_ro_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                        else $this->process_host_list('REVOKE', $data['new']['database_name'], $db_ro_user['database_user'], $db_ro_user['database_password'], $data['new']['remote_ips'], $link);
+                    }
 				}
 				$app->log('Changing MySQL remote access privileges 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 remote access list
+                if($db_user) {
+                    if($db_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else {
+                        $this->process_host_list('REVOKE', $data['new']['database_name'], $db_user['database_user'], $db_user['database_password'], $data['old']['remote_ips'], $link);
+                        $this->process_host_list('GRANT', $data['new']['database_name'], $db_user['database_user'], $db_user['database_password'], $data['new']['remote_ips'], $link);
+                    }
+                }
+                if($db_ro_user && $data['new']['database_user_id'] != $data['new']['database_ro_user_id']) {
+                    if($db_ro_user['database_user'] == 'root') $app->log('User root not allowed for Client databases',LOGLEVEL_WARNING);
+                    else {
+                        $this->process_host_list('REVOKE', $data['new']['database_name'], $db_ro_user['database_user'], $db_ro_user['database_password'], $data['old']['remote_ips'], $link);
+                        $this->process_host_list('GRANT', $data['new']['database_name'], $db_ro_user['database_user'], $db_ro_user['database_password'], $data['new']['remote_ips'], $link, '', true);
+                    }
+                }
+          }
       
-			//* Change password
-			if($data['new']['database_password'] != $data['old']['database_password']) {
-				$db_host = 'localhost';
-				$link->query("SET PASSWORD FOR '".$link->escape_string($data['new']['database_user'])."'@'$db_host' = '".$link->escape_string($data['new']['database_password'])."';");
-
-				if($data['new']['remote_access'] == 'y') {
-					$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);
-			}
 			
 			$link->query('FLUSH PRIVILEGES;');
 			$link->close();
@@ -282,21 +326,6 @@
 				return;
 			}
 			
-			//* Get the db host setting for the access priveliges
-			if($data['old']['remote_access'] == 'y') {
-			 	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'].' '.$link->error,LOGLEVEL_WARNING);
-				}
-			}
-			$db_host = 'localhost';
-			if($link->query("DROP USER '".$link->escape_string($data['old']['database_user'])."'@'$db_host';")) {
-				$app->log('Dropping MySQL user: '.$data['old']['database_user'],LOGLEVEL_DEBUG);
-			} else {
-				$app->log('Error while dropping MySQL user: '.$data['old']['database_user'].' '.$link->error,LOGLEVEL_WARNING);
-			}
-			
 			if($link->query('DROP DATABASE '.$link->escape_string($data['old']['database_name']))) {
 				$app->log('Dropping MySQL database: '.$data['old']['database_name'],LOGLEVEL_DEBUG);
 			} else {
@@ -310,24 +339,100 @@
 		
 	}
 	
-	/*
+	 
 	function db_user_insert($event_name,$data) {
 		global $app, $conf;
-		
+		// we have nothing to do here, stale user accounts are useless ;)
 	}
 	
 	function db_user_update($event_name,$data) {
 		global $app, $conf;
 		
+        if(!include(ISPC_LIB_PATH.'/mysql_clientdb.conf')) {
+            $app->log('Unable to open'.ISPC_LIB_PATH.'/mysql_clientdb.conf',LOGLEVEL_ERROR);
+            return;
+        }
+        
+        //* Connect to the database
+        $link = new mysqli($clientdb_host, $clientdb_user, $clientdb_password);
+        if ($link->connect_error) {
+            $app->log('Unable to connect to mysql'.$link->connect_error,LOGLEVEL_ERROR);
+            return;
+        }
+        
+        
+        if($data['old']['database_user'] == $data['new']['database_user'] && $data['old']['database_password'] == $data['new']['database_password']) {
+            return;
+        }
+        
+        
+        $host_list = array('localhost');
+        // get all databases this user was active for
+        $db_list = $app->db->queryAllRecords("SELECT `remote_access`, `remote_ips` FROM `web_database` WHERE `database_user_id` = '" . intval($data['old']['database_user_id']) . "'");
+        foreach($db_list as $database) {
+            if($database['remote_access'] != 'y') continue;
+            
+            if($database['remote_ips'] != '') $ips = explode(',', $database['remote_ips']);
+            else $ips = array('%');
+            
+            foreach($ips as $ip) {
+                $ip = trim($ip);
+                if(!in_array($ip, $host_list)) $host_list[] = $ip;
+            }
+        }
+        
+        foreach($host_list as $db_host) {
+            if($data['new']['database_user'] != $data['old']['database_user']) {
+				$link->query("RENAME USER '".$link->escape_string($data['old']['database_user'])."'@'$db_host' TO '".$link->escape_string($data['new']['database_user'])."'@'$db_host'");
+				$app->log('Renaming MySQL user: '.$data['old']['database_user'].' to '.$data['new']['database_user'],LOGLEVEL_DEBUG);
+			}
+
+			if($data['new']['database_password'] != $data['old']['database_password']) {
+				$db_host = 'localhost';
+				$link->query("SET PASSWORD FOR '".$link->escape_string($data['new']['database_user'])."'@'$db_host' = '".$link->escape_string($data['new']['database_password'])."';");
+				$app->log('Changing MySQL user password for: '.$data['new']['database_user'],LOGLEVEL_DEBUG);
+			}
+        }
+        
+        $link->query('FLUSH PRIVILEGES;');
+        $link->close();
+        
 	}
 	
 	function db_user_delete($event_name,$data) {
 		global $app, $conf;
 		
+        if(!include(ISPC_LIB_PATH.'/mysql_clientdb.conf')) {
+            $app->log('Unable to open'.ISPC_LIB_PATH.'/mysql_clientdb.conf',LOGLEVEL_ERROR);
+            return;
+        }
+        
+        //* Connect to the database
+        $link = new mysqli($clientdb_host, $clientdb_user, $clientdb_password);
+        if ($link->connect_error) {
+            $app->log('Unable to connect to mysql'.$link->connect_error,LOGLEVEL_ERROR);
+            return;
+        }
+        
+        $host_list = array();
+        // read all mysql users with this username
+        $result = $link->query("SELECT `User`, `Host` FROM `mysql`.`user` WHERE `User` = '" . $link->escape_string($data['old']['database_user']) . "' AND `Create_user_priv` = 'N'"); // basic protection against accidently deleting system users like debian-sys-maint
+        if($result) {
+            while($row = $result->fetch_assoc()) {
+                $host_list[] = $row['Host'];
+            }
+            $result->free();
+        }
+        
+        foreach($host_list as $db_host) {
+            if($link->query("DROP USER '".$link->escape_string($data['old']['database_user'])."'@'$db_host';")) {
+				$app->log('Dropping MySQL user: '.$data['old']['database_user'],LOGLEVEL_DEBUG);
+            }
+        }
+        
+        $link->query('FLUSH PRIVILEGES;');
+        $link->close();
 	}
-	*/
-	
-
 } // end class
 
 ?>

--
Gitblit v1.9.1