[packaging] Branch 'debian/hosted-kolab-webadmin' - debian/patches

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Sun Dec 5 17:21:03 CET 2010


 debian/patches/0001-Add-customers-to-the-hosted-version-of-the-kolab-web.patch | 2207 ++++++++++
 debian/patches/0002-Add-quota-on-a-per-customer-basis.patch                    |  383 +
 debian/patches/0003-Mail-aliases-must-match-a-domain-name-space-matching.patch |  102 
 debian/patches/0004-Add-the-kolabHomeServer-per-customer.patch                 |  256 +
 debian/patches/0005-Fix-merge-issues.patch                                     |   44 
 debian/patches/series                                                          |    5 
 6 files changed, 2997 insertions(+)

New commits:
commit f5bb2099c4f8a945239fa43a640f46d0fa78b6a5
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Sun Dec 5 16:00:15 2010 +0000

    Add the patches as we use them in the RPMs

diff --git a/debian/patches/0001-Add-customers-to-the-hosted-version-of-the-kolab-web.patch b/debian/patches/0001-Add-customers-to-the-hosted-version-of-the-kolab-web.patch
new file mode 100644
index 0000000..e6a8424
--- /dev/null
+++ b/debian/patches/0001-Add-customers-to-the-hosted-version-of-the-kolab-web.patch
@@ -0,0 +1,2207 @@
+From f7f262c8246150d82c4488a3da866f59c8733ab3 Mon Sep 17 00:00:00 2001
+From: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
+Date: Tue, 9 Nov 2010 17:13:38 +0000
+Subject: [PATCH 1/5] Add customers to the hosted version of the kolab webadmin
+
+---
+ Makefile.am                                        |    1 +
+ kolab-webadmin.spec.in                             |    2 +-
+ php/admin/include/auth.class.php.in                |   72 +++++
+ php/admin/include/customer.php                     |  333 ++++++++++++++++++++
+ php/admin/include/form.class.php                   |    5 +-
+ php/admin/include/ldap.class.php.in                |  215 +++++++++++++-
+ php/admin/include/locale.php                       |   15 +
+ php/admin/include/menu.php                         |    4 +-
+ php/admin/include/mysmarty.php.in                  |   46 +++
+ php/admin/locale/de/LC_MESSAGES/messages.po        |   72 +++++
+ php/admin/templates/distlistall.tpl                |    5 +-
+ php/admin/templates/page.tpl.in                    |   17 +
+ php/admin/templates/settings.tpl                   |  181 ++++++++---
+ php/admin/templates/sflistall.tpl                  |   11 +-
+ www/admin/addressbook/addr.php.in                  |   17 +-
+ www/admin/addressbook/index.php.in                 |   11 +-
+ www/admin/distributionlist/index.php.in            |   28 ++-
+ www/admin/distributionlist/list.php.in             |   18 +-
+ www/admin/domainmaintainer/domainmaintainer.php.in |   39 ++-
+ www/admin/settings/index.php.in                    |  223 ++++++++++++--
+ www/admin/sharedfolder/index.php.in                |   20 +-
+ www/admin/sharedfolder/sf.php.in                   |   22 +-
+ www/admin/user/index.php.in                        |   19 +-
+ www/admin/user/user.php.in                         |   45 +++-
+ 24 files changed, 1299 insertions(+), 122 deletions(-)
+ create mode 100644 php/admin/include/customer.php
+
+diff --git a/Makefile.am b/Makefile.am
+index 70669e4..5df69aa 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -112,6 +112,7 @@ PHP_INCLUDES = php/admin/include/menu.php \
+ 	php/admin/include/authenticate.php \
+ 	php/admin/include/locale.php \
+ 	php/admin/include/Sieve.php \
++	php/admin/include/customer.php \
+ 	php/admin/include/passwd.php
+ 
+ phpincludesdir = $(phpkolabdir)/include
+diff --git a/kolab-webadmin.spec.in b/kolab-webadmin.spec.in
+index d08b143..21b4a66 100644
+--- a/kolab-webadmin.spec.in
++++ b/kolab-webadmin.spec.in
+@@ -31,7 +31,7 @@ Distribution: OpenPKG
+ Group:        Mail
+ License:      GPL
+ Version:      @VERSION@
+-Release:      @spec_build_date@
++Release:      @spec_build_date at _hosted1
+ 
+ 
+ #   list of sources
+diff --git a/php/admin/include/auth.class.php.in b/php/admin/include/auth.class.php.in
+index 7ecfc1a..338c1bf 100644
+--- a/php/admin/include/auth.class.php.in
++++ b/php/admin/include/auth.class.php.in
+@@ -64,7 +64,35 @@ class KolabAuth {
+ 				}
+ 				if( $dn ) {
+ 					$auth_user = $ldap->uidForDn( $dn );
++					$customer_dn = $ldap->dnForCustomer( $dn );
+ 					$auth_group = $ldap->groupForUid( $auth_user );
++					if($auth_group == 'domain-maintainer')
++						$customer_dns = $ldap->dnForCustomers($dn, true);
++					else if($auth_group == 'maintainer' || $auth_group == 'admin') {
++						$customer_dns = $ldap->dnForCustomers('', true);
++						array_unshift($customer_dns, array(
++							'dn'          => $_SESSION['base_dn'],
++							'descr'       => _('No customer'),
++							'subtree'     => '',
++							'ignore'      => 0
++						));
++						if(count($customer_dns) > 1)
++							array_unshift($customer_dns, array(
++								'dn'          => $_SESSION['base_dn'],
++								'descr'       => _('Everything'),
++								'subtree'     => '',
++								'ignore'      => 1
++							));
++						if(count($customer_dns) > 1)
++							array_unshift($customer_dns, array(
++								'dn'          => $_SESSION['base_dn'],
++								'descr'       => '----------------',
++								'subtree'     => '',
++								'ignore'      => 2
++							));
++					}
++					else
++						$customer_dns = array();
+ 					$tmp_group = ($auth_user=='manager')?'manager':$auth_group;
+ 					if( !in_array( $tmp_group, $this->params['allow_user_classes'] ) ) {
+ 						$this->error_string = _("User class '$tmp_group' is denied access");
+@@ -78,6 +106,23 @@ class KolabAuth {
+ 						$_SESSION['auth_pw'] = $_POST['password'];
+ 						$_SESSION['auth_group'] = $auth_group;
+ 						$_SESSION['remote_ip'] = $_SERVER['REMOTE_ADDR'];
++						if(empty($customer_dn))
++							$customer_dn = $_SESSION['base_dn'];
++						if(!empty($customer_dn)) {
++							$_SESSION['customer_dn'] = $customer_dn;
++							$_SESSION['customer_subtree'] = $customer_dn ==
++									$_SESSION['base_dn'] ? '' : substr($customer_dn, 0,
++									strlen($customer_dn) - strlen($_SESSION['base_dn']))
++									. 'cn=customers,cn=internal,' . $_SESSION['base_dn'];
++						}
++						$_SESSION['customer_dn_options'] = $customer_dns;
++						sortCustomerList();
++						$_SESSION['ignore_customer'] = $auth_group == 'maintainer'
++								|| $auth_group == 'admin';
++						if(isset($_SESSION['customer_dn']) || $auth_group == "user") {
++							// we sorted stuff, better safe than sorry
++							switchToCustomer(0);
++						}
+ 						return true;
+ 					} else {
+ 						$this->error_string = _("Wrong username or password");
+@@ -165,6 +210,33 @@ class KolabAuth {
+ 	var $error_string = false;
+ 	var $params;
+ };
++
++function _customer_less($a, $b) {
++	$head = array(
++		_('Everything'),
++		_('No customer'),
++		'----------------'
++	);
++	$apos = array_search($a['descr'], $head);
++	$bpos = array_search($b['descr'], $head);
++	$found = ($apos === FALSE ? 'n' : 'y') . ($bpos === FALSE ? 'n' : 'y');
++	switch($found) {
++		case 'yy':
++			return ($apos > $bpos ? 1 : 0) - ($bpos > $apos ? 1 : 0);
++		case 'yn':
++			return -1;
++		case 'ny':
++			return 1;
++	}
++	return strcasecmp($a['descr'], $b['descr']);
++}
++
++function sortCustomerList() {
++	$opt = $_SESSION['customer_dn_options'];
++	usort($opt, '_customer_less');
++	$_SESSION['customer_dn_options'] = $opt;
++}
++
+ /*
+   Local variables:
+   mode: php
+diff --git a/php/admin/include/customer.php b/php/admin/include/customer.php
+new file mode 100644
+index 0000000..e99def4
+--- /dev/null
++++ b/php/admin/include/customer.php
+@@ -0,0 +1,333 @@
++<?php
++/*
++ (c) 2008 TBits.net GmbH
++ (c) 2008 Martin Zapfl <mz at tbits.net>
++ This program is Free Software under the GNU General Public License (>=v2).
++ Read the file COPYING that comes with this packages for details.
++*/
++
++// List all customers
++// Show "no customer" by defalt
++function getCustomerList($add_choose = true, $customer_id = ""){
++  global $ldap;
++  if ( $add_choose ) $item_count = 1;
++  else $item_count = 0;
++  $array_count = 0;
++  if ($add_choose) $customer[] = _("No customer");
++  // Output a sorted customer field
++  $result = $ldap->search( "cn=customers,cn=internal,".$_SESSION['base_dn'],"(objectClass=kolabGroupOfNames)",array("cn","description"));
++  ldap_sort($ldap->connection,$result,'description');
++  if($result){
++    foreach ($ldap->getEntries() as $v){
++      if(is_array($v)){
++			  if ($customer_id != "" && $item_count == $customer_id && $v["cn"][0] != "" ) { 
++				  return $v["cn"][0];
++				}	
++       $customer[] = $v["description"][0];
++       $item_count++;
++      }
++    }
++    return $customer_id ? false : $customer;
++  } else return $customer;
++}
++
++// Return list of (Customer CN,
++// Customer Description) pairs
++// for given optional maintainer
++function getNamedCustomerList($maintainer = '') {
++	global $ldap;
++	$cns = array();
++	$descriptions = array();
++	$filter = '(objectClass=kolabGroupOfNames)';
++	if($maintainer != '')
++		$filter = "(&$filter(member=" . $ldap->escape($maintainer) . '))';
++	$result = $ldap->search('cn=customers,cn=internal,' . $_SESSION['base_dn'],
++		$filter, array('cn', 'description'));
++	if($result) {
++		ldap_sort($ldap->connection, $result, 'description');
++		foreach($ldap->getEntries() as $entry) {
++			$cn = $entry['cn'];
++			if($cn == '')
++				continue;
++			$cns[] = is_array($cn) ? $cn[0] : $cn;
++			$desc = $entry['description'];
++			$descriptions[] = is_array($desc) ? $desc[0] : $desc;
++		}
++	}
++	return array('cns' => $cns, 'descriptions' => $descriptions);
++}
++
++// Get customer description for a customer dn
++function customerDnToDescription($customer_dn){
++  global $ldap;
++  if (!preg_match("/".$_SESSION['base_dn']."/", $customer_dn)) $customer_dn .= ",cn=customers,cn=internal,".$_SESSION['base_dn'];
++  $result = $ldap->search( $customer_dn,"(description=*)", array("description") );
++  $entry = $ldap->getEntries();
++  return ($entry[0]['description'][0])?$entry[0]['description'][0]:_("No customer");
++}
++
++// Get customer description for a member
++function memberToCustomerDescription($member_dn){
++  global $ldap;
++  $result = $ldap->search( "cn=customers,cn=internal,".$_SESSION['base_dn'],"(member=".$member_dn.")", array("description") );
++  $entry = $ldap->getEntries();
++  return ($entry[0]['description'][0])?$entry[0]['description'][0]:_("No customer");
++}
++
++// Get UID Prefix for a customer dn
++function getPrefixForCustomer($customer_dn){
++    global $ldap;
++    $res = ldap_read( $ldap->connection, $customer_dn,
++					  '(objectclass=*)',
++					  array( 'uidPrefix' ) );
++	if( $res ) {
++	  $entries = ldap_get_entries( $ldap->connection, $res );
++	  ldap_free_result( $res );
++	  if( $entries['count'] == 1 ) {
++		return $entries[0]['uidprefix'][0];
++	  } else {
++		return false;
++	  }
++	} else {
++	  return false;
++	}
++    return false;	
++}
++
++// Get customer dn for select field id
++// this function is stupid and should be superfluous, but i dare not remove it  -- simon
++function getCustomerDnFromId($id){
++	$id = trim($id);
++	if(is_numeric($id) && $id > 0){
++		$customer_tree = getCustomerList(true, $id);
++		return $customer_tree ? "cn=$customer_tree,".$_SESSION['base_dn'] : false;
++	} else return $_SESSION['base_dn'];
++}
++
++// Get customer ID from a dn
++// this function is stupid and should be superfluous, but i dare not remove it  -- simon
++function getCustomerIdFromDn($dn){
++	global $ldap;
++	preg_match("/cn=([a-zA-Z0-9]+),".$_SESSION["base_dn"]."$/",$dn,$matches);
++	$customer_cn = $matches[count($matches)-1];
++	$result = $ldap->search( "cn=customers,cn=internal,".$_SESSION['base_dn'],"(cn=".$customer_cn.")", array("description") );
++	$entry = $ldap->getEntries();
++	if ($entry["count"] > 0){
++		$customer_desc = $entry[0]["description"][0];
++		foreach(getCustomerList(true) as $id => $customer){
++			if ($customer_desc == $customer) return $id;
++		}
++	} else {
++		return "0";
++	}
++}
++
++function arrayInsideOut($a) {
++	$b = array();
++	foreach($a as $row_key => $row)
++		foreach($row as $col_key => $col)
++			if(!isset($b[$col_key]))
++				$b[$col_key] = array($row_key => $col);
++			else
++				$b[$col_key][$row_key] = $col;
++	return $b;
++}
++
++function getSelectedCustomerCN() {
++	$base = $_SESSION['base_dn'];
++	$cdn = $_SESSION['customer_dn'];
++	if($base == $cdn)
++		return '';
++	return substr($cdn, 3, strlen($cdn) - strlen($base) - 4);
++}
++
++function removeDomainFromAllCustomers($domain) {
++	global $ldap;
++	$domain = canonicalizeEntity($domain, 'domains');
++	$result = ldap_list($ldap->connection, 'cn=customers,cn=internal,'
++			. $_SESSION['base_dn'],
++			"(&(objectClass=kolabGroupOfNames)(domains=$domain))",
++			array('cn'));
++	if(!$result)
++		return ldap_error($ldap->connection);
++	$entry = ldap_first_entry($ldap->connection, $result);
++	for(; $entry; $entry = ldap_next_entry($ldap->connection, $entry)) {
++		$cn = ldap_get_values($ldap->connection, $entry, 'cn');
++		$cn = $cn[0];
++		if($error = removeDomainFromCustomer($domain, $cn)) {
++			ldap_free_result($result);
++			return $error;
++		}
++	}
++	ldap_free_result($result);
++	return false;
++}
++
++function canonicalizeEntity($entity, $path) {
++	$base = ',' . $_SESSION['base_dn'];
++	$intern = ",cn=$path,cn=internal" . $base;
++	if(strlen($entity) > strlen($intern) &&
++			substr($entity, strlen($entity) - strlen($intern)) == $intern)
++		$entity = substr($entity, 0, strlen($entity) - strlen($intern));
++	else if(strlen($entity) > strlen($base) &&
++			substr($entity, strlen($entity) - strlen($base)) == $base)
++		$entity = substr($entity, 0, strlen($entity) - strlen($base));
++	if(strlen($entity) > 3 && substr($entity, 0, 3) == 'cn=')
++		$entity = substr($entity, 3);
++	return $entity;
++}
++
++function removeDomainFromCustomer($domain, $customer, $add = false) {
++	global $ldap;
++	$domain = canonicalizeEntity($domain, 'domains');
++	$customer = canonicalizeEntity($customer, 'customers');
++	if(!strlen($customer))
++		return;
++	$base = $_SESSION['base_dn'];
++	$result = ldap_read($ldap->connection,
++			"cn=$customer,cn=customers,cn=internal,$base",
++			'(objectClass=kolabGroupOfNames)');
++	if(!$result)
++		return ldap_error($ldap->connection);
++	$entry = ldap_get_entries($ldap->connection, $result);
++	if(!$entry) {
++		ldap_free_result($result);
++		return ldap_error($ldap->connection);
++	}
++	ldap_free_result($result);
++	$entry = $entry[0];
++	$dn = $entry['dn'];
++	unset($entry['dn']);
++	$count = $entry['count'];
++	unset($entry['count']);
++	for($i = 0; $i < $count; ++$i)
++		unset($entry[$i]);
++	foreach($entry as $key => $value)
++		unset($entry[$key]['count']);
++	if(!isset($entry['domains']))
++		$domains = array();
++	else
++		$domains = $entry['domains'];
++	if($add)
++		if(in_array($domain, $domains))
++			return false;
++		else
++			$domains[] = $domain;
++	else
++		if(($index = array_search($domain, $domains)) === FALSE)
++			return false;
++		else
++			unset($domains[$index]);
++	$entry['domains'] = array_values($domains);
++	if(ldap_modify($ldap->connection, $dn, $entry) === FALSE)
++		return ldap_error($ldap->connection);
++	return false;
++}
++
++function addDomainToCustomer($domain, $customer) {
++	return removeDomainFromCustomer($domain, $customer, true);
++}
++
++function setDomainOwner($domain, $customer) {
++	if($error = removeDomainFromAllCustomers($domain))
++		return $error;
++	return addDomainToCustomer($domain, $customer);
++}
++
++function findCustomerInSessionArrayByCN($cn) {
++	$idx = 0;
++	$dn = "cn=$cn," . $_SESSION['base_dn'];
++	foreach($_SESSION['customer_dn_options'] as $option)
++		if($option['dn'] == $dn)
++			return $idx;
++		else
++			++$idx;
++	return -1;
++}
++
++function adjustSessionForCustomerUpdate($customer, $action, $descr = null, $server = null, $nogroup = false) {
++	$cdn = "cn=$customer," . $_SESSION['base_dn'];
++	switch($action) {
++		case 'added':
++			$info = array(
++				'dn' => $cdn,
++				'descr' => $descr,
++				'subtree' => "cn=$customer,cn=customers,cn=internal,"
++						. $_SESSION['base_dn'],
++				'ignore' => 0,
++			);
++			$_SESSION['customer_dn_options'][] = $info;
++			sortCustomerList();
++			break;
++		case 'removed':
++			$index = findCustomerInSessionArrayByCN($customer);
++			if($index == -1)
++				break;
++			$data = $_SESSION['customer_dn_options'];
++			unset($data[$index]);
++			$_SESSION['customer_dn_options'] = array_values($data);
++			if($_SESSION['customer_dn'] == $cdn)
++				switchToCustomer(0);
++			break;
++		case 'modified':
++			$index = findCustomerInSessionArrayByCN($customer);
++			if($index == -1)
++				break;
++			$_SESSION['customer_dn_options'][$index]['descr'] = $descr;
++			break;
++	}
++}
++
++function getCurrentCustomerQuota() {
++	$customer = getSelectedCustomerCN();
++	if(!$customer)
++		return 0;
++	return getCustomerQuota($customer);
++}
++
++function getCustomerQuota($customer) {
++	global $ldap;
++	if(!$customer)
++		return 0;
++	$dn = "cn=$customer,cn=customers,cn=internal," . $_SESSION['base_dn'];
++	$attr = $ldap->read($dn);
++	if(!$attr)
++		return 0;
++	$quota = $attr['customerQuota'][0];
++	return $quota ? $quota : 0;
++}
++
++function getCustomerUsedDomainQuota($customer) {
++	global $ldap;
++	if(!$customer)
++		return 0;
++	$domains = $ldap->domainsOfCustomer($customer);
++	$used = 0;
++	foreach($domains as $domain)
++		$used += getCustomerUsedDomainQuotaByDomain($domain);
++	return $used;
++}
++
++function getCustomerUsedDomainQuotaByDomain($domain) {
++	$quotas = getDomainQuotas($domain);
++	$dquota = $quotas ? $quotas['domainquota'] : 0;
++	if($dquota)
++		return $dquota;
++	return getDomainQuotaInUse($domain);
++}
++
++function getValidAliasDomainsForUser($user_dn) {
++	global $ldap;
++	$base_dn = $_SESSION['base_dn'];
++	$tree = ldap_explode_dn($user_dn, 0);
++	unset($tree['count']);
++	array_shift($tree);
++	$base_tree = ldap_explode_dn($base_dn, 0);
++	unset($base_tree['count']);
++	if(count($tree) == count($base_tree))
++		return $ldap->domainsOfNoCustomer();
++	$customer = substr(array_shift($tree), 3);
++	return $ldap->domainsOfCustomer($customer);
++}
++
++?>
+diff --git a/php/admin/include/form.class.php b/php/admin/include/form.class.php
+index 38751f1..00837de 100644
+--- a/php/admin/include/form.class.php
++++ b/php/admin/include/form.class.php
+@@ -217,12 +217,15 @@ class KolabForm {
+ 	  $str .= '<td><span class="ctrl">'.(join('<br/>',$value['options'])).'</span></td>';
+ 	} else {
+ 	  $str .= '<td><table class="contentform">';
++	  $opt_index = 0;
+ 	  foreach( $value['options'] as $opt ) {
+ 	    if( is_array( $value['value'] ) ) $checked = ( in_array($opt ,$value['value'] ))?"checked":"";
+ 	    else $checked = "";
+ 	    //debug("Checking if $opt is in ".join(",",$value['value'])." :$checked");
+ 	    $str .= '<tr><td><input name="'.$key.'[]" type="checkbox" value="'.MySmarty::htmlentities($opt).'" '.$value['attrs']." $checked /></td><td>"
+-	      .MySmarty::htmlentities($opt).'</td></tr>';
++	      . MySmarty::htmlentities($value['names'] ? $value['names'][$opt_index] : $opt)
++	      . '</td></tr>';
++	    ++$opt_index;
+ 	  }
+ 	  $str .= '</table></td>';
+ 	}
+diff --git a/php/admin/include/ldap.class.php.in b/php/admin/include/ldap.class.php.in
+index 829d7d8..ad32bce 100644
+--- a/php/admin/include/ldap.class.php.in
++++ b/php/admin/include/ldap.class.php.in
+@@ -277,7 +277,11 @@ class KolabLDAP {
+   }
+ 
+   function dnForMail( $mail ) {
+-    if( $this->search( $_SESSION['base_dn'],
++    if(isset($_SESSION['customer_dn']))
++      $base_dn = $_SESSION['customer_dn'];
++    else
++      $base_dn = $_SESSION['base_dn'];
++    if( $this->search( $base_dn,
+                        '(&(objectclass=kolabInetOrgPerson)(mail='.$this->escape($mail).'))' ) ) {
+       $entry = $this->firstEntry();
+       if( $entry ) {
+@@ -307,7 +311,11 @@ class KolabLDAP {
+   }
+ 
+   function dnForAlias( $mail ) {
+-    if( $this->search( $_SESSION['base_dn'],
++    if(isset($_SESSION['customer_dn']))
++      $base_dn = $_SESSION['customer_dn'];
++    else
++      $base_dn = $_SESSION['base_dn'];
++    if( $this->search( $base_dn,
+                        '(&(objectclass=kolabInetOrgPerson)(alias='.$this->escape($mail).'))' ) ) {
+       $entry = $this->firstEntry();
+       if( $entry ) {
+@@ -353,6 +361,48 @@ class KolabLDAP {
+     return $group;
+   }
+ 
++  function dnForCustomer($dn) {
++    if(!$this->is_bound)
++      return false;
++    $result = $this->search('cn=customers,cn=internal,' . $_SESSION['base_dn'],
++        '(&(objectclass=kolabGroupOfNames)(member='. $this->escape($dn). '))');
++    $entries = $this->getEntries();
++    unset($entries['count']);
++    if(count($entries) > 0)
++      foreach($entries as $val) {
++        $cust_dn = str_replace(",cn=customers,cn=internal,"
++            . $_SESSION["base_dn"], "", $val["dn"]);
++        return $cust_dn . "," . $_SESSION['base_dn'];
++      }
++  }
++
++  function dnForCustomers($dn = '', $withDesc = false) {
++	if(!$this->is_bound)
++	  return false;
++	$filter = $dn == '' ? '' : '(member=' . $this->escape($dn) . ')';
++	$result = $this->search('cn=customers,cn=internal,' . $_SESSION['base_dn'],
++	    "(&(objectClass=kolabGroupOfNames)$filter)",
++		array('dn', 'description', 'disableGroupware', 'uidPrefix', 'kolabHomeServer'));
++	if(!$result)
++	  return array();
++	$entries = $this->getEntries();
++	unset($entries['count']);
++	$customers = array();
++	foreach($entries as $val) {
++	  $c_dn = str_replace(',cn=customers,cn=internal,', ',', $val['dn']);
++	  if($withDesc)
++	    $customers[] = array(
++		  'dn'          => $c_dn,
++		  'descr'       => $val['description'][0],
++		  'subtree'     => $val['dn'],
++		  'ignore'      => 0
++		);
++	  else
++	    $customers[] = $c_dn;
++	}
++	return $customers;
++  }
++
+   function domainsForMaintainerDn( $dn ) {
+     if( !$this->is_bound ) {
+       return false;
+@@ -373,6 +423,21 @@ class KolabLDAP {
+ 	return $domains;
+   }
+ 
++  function customersForMaintainerDn($dn) {
++    $count = 0;
++    $result = $this->search('cn=customers,cn=internal,' . $_SESSION['base_dn'],
++        '(member=' . $this->escape($dn) . ')', array('cn'));
++    $cns = array();
++    if($result)
++      foreach($this->getEntries() as $entry) {
++        $cn = $entry['cn'];
++        $cn = is_array($cn) ? $cn[0] : $cn;
++        if($cn != '')
++          $cns[] = $cn;
++      }
++    return $cns;
++  }
++
+   function domainsForMaintainerUid( $uid ) {
+ 	debug("domainsForMaintainer( $uid ):");
+     $dn = $this->dnForUid($uid);
+@@ -429,6 +494,10 @@ class KolabLDAP {
+ 		debug("found ".$entries[$i]['dn'] );
+ 		$count++;
+ 	  } 
++	  // you forgot that you counted $entries['count']
++	  // as well; how the fsck(8) did nobody notice
++	  // that until now?
++	  --$count;
+ 	} else $count += $entries['count'];
+ 	
+ 	/* Distribution lists have a mail attr now too,
+@@ -471,6 +540,81 @@ class KolabLDAP {
+ 	return $this->cached_domains;
+   }
+ 
++  function domainsOfSelectedCustomer() {
++    $cdn = $_SESSION['customer_dn'];
++    $base = $_SESSION['base_dn'];
++    if($cdn == $base)
++      if($_SESSION['ignore_customer'])
++        return $this->domains(true);
++      else
++        return $this->domainsOfNoCustomer();
++    $idn = substr($cdn, 0, strlen($cdn) - strlen($base))
++        . 'cn=customers,cn=internal,' . $base;
++    $obj = $this->read($idn);
++    if(!$obj)
++      return array();
++    $obj = $obj['domains'];
++    unset($obj['count']);
++    sort($obj);
++    return $obj;
++  }
++
++	function accessibleDomainsOfSelectedCustomer() {
++		global $auth;
++		$cust = $this->domainsOfSelectedCustomer();
++		if($auth->group() != 'domain-maintainer')
++			return $cust;
++		$main = $this->domainsForMaintainerDn($auth->dn());
++		return array_intersect($cust, $main);
++	}
++
++  function domainsOfCustomer($customer) {
++    $dn = "cn=$customer,cn=customers,cn=internal," . $_SESSION['base_dn'];
++    $obj = $this->read($dn);
++    if(!$obj)
++      return ldap_error($this->connection);
++	if(!isset($obj['domains']))
++		return array();
++    $obj = $obj['domains'];
++    unset($obj['count']);
++    sort($obj);
++    return $obj;
++  }
++
++  function domainsOfNoCustomer() {
++    $all = $this->domains(true);
++    $domains = array();
++    foreach($all as $domain) {
++      $result = ldap_list($this->connection, 'cn=customers,cn=internal,'
++          . $_SESSION['base_dn'], "(&(objectClass=kolabGroupOfNames)"
++          . "(domains=$domain))");
++      if(!$result)
++        continue;
++      $count = ldap_count_entries($this->connection, $result);
++      if($count !== FALSE && !$count)
++        $domains[] = $domain;
++      ldap_free_result($result);
++    }
++    sort($domains);
++    return $domains;
++  }
++
++  function customerOfDomain($domain) {
++    $result = $this->search('cn=customers,cn=internal,' . $_SESSION['base_dn'],
++        "(&(objectClass=kolabGroupOfNames)(domains=$domain))", array('cn'));
++    if(!$result)
++      return '';
++    $result = $this->getEntries();
++    return $result['count'] ? $result[0]['cn'][0] : '';
++  }
++
++  function customerNameOfDomain($domain) {
++    $result = $this->search('cn=customers,cn=internal,' . $_SESSION['base_dn'],
++        "(&(objectClass=kolabGroupOfNames)(domains=$domain))",
++        array('description'));
++    return $result['count'] ? $result[0]['description'][0] : _('No customer');
++  }
++
+   function addToDomainGroups( $member, $domains ) {
+ 	if (empty($domains)) {
+ 	  return true;
+@@ -500,6 +644,22 @@ class KolabLDAP {
+ 	return true;
+   }
+ 
++  function addToCustomerGroups($member, $customers) {
++    if(empty($customers))
++      return true;
++    foreach($customers as $customer) {
++      $cusgrpdn = 'cn=' . $this->dn_escape($customer) . ',cn=customers,cn=internal,'
++          . $_SESSION['base_dn'];
++      $cus_obj = $this->read($cusgrpdn);
++      if(!$cus_obj)
++        return false;
++      if(!in_array($member, $cus_obj['member']))
++        if(!ldap_mod_add($this->connection, $cusgrpdn, array('member' => $member)))
++          return false;
++    }
++    return true;
++  }
++
+   function removeFromDomainGroups( $member, $domains ) {
+ 	if (empty($domains)) {
+ 	  return true;
+@@ -525,10 +685,57 @@ class KolabLDAP {
+ 	}	
+   }
+ 
++  function removeFromCustomerGroups($member, $customers) {
++    if(empty($customers))
++      return true;
++    foreach($customers as $customer) {
++      $cusgrpdn = 'cn=' . $this->dn_escape($customer) . ',cn=customers,cn=internal,'
++          . $_SESSION['base_dn'];
++      $cus_obj = $this->read($cusgrpdn);
++      if($cus_obj)
++        if(!ldap_mod_del($this->connection, $cusgrpdn, array('member' => $member)))
++          return false;
++    }
++    return true;
++  }
++
++  function removeFromCustomerGroup($member) {
++    $custgrpdn = 'cn=customers,cn=internal,' . $_SESSION['base_dn'];
++    $result = $this->search($custgrpdn, "(&(objectClass=kolabGroupOfNames)(member=$member))",
++        array("cn"));
++    if(ldap_count_entries($this->connection,$result) > 0) {
++      $remove_list = $this->getEntries();
++      unset($remove_list["count"]);
++      foreach($remove_list as $v) {
++        $result = ldap_mod_del($this->connection, "cn=" . $v['cn'][0] .
++            ",cn=customers,cn=internal," . $_SESSION['base_dn'],
++            array('member' => $member));
++        if(!$result)
++          return false;
++      }
++    }
++  }
++
++  function addToCustomerGroup($member, $customer_id) {
++    $customer = getCustomerList(true, $customer_id);
++    if(empty($customer_id) || (empty($customer) || is_array($customer)))
++      return true;
++    $custgrpdn = 'cn=' . $customer . ',cn=customers,cn=internal,'
++        . $_SESSION['base_dn'];
++    $result = $this->search($custgrpdn, "(&(objectClass=kolabGroupOfNames)"
++        . "(member=$member))");
++    if(ldap_count_entries($this->connection,$result) <= 0) {
++      if(!ldap_mod_add($this->connection, "cn=" . $customer . ",cn=customers,cn=internal,"
++          . $_SESSION['base_dn'], array('member' => $member)))
++        return false;
++    }
++    return true;
++  }
++
+   // Set deleflag on object, or if $delete_now is
+   // true, just delete it
+-  function deleteObject( $dn, $delete_now = false ) {
+-	return $this->_doDeleteObject( $dn, $delete_now, true );
++  function deleteObject( $dn, $delete_now = false, $nukepw = true ) {
++	return $this->_doDeleteObject( $dn, $delete_now, $nukepw );
+   }
+ 
+   function deleteSharedFolder( $dn, $delete_now = false ) {
+diff --git a/php/admin/include/locale.php b/php/admin/include/locale.php
+index ce7e7fd..6caf4d3 100644
+--- a/php/admin/include/locale.php
++++ b/php/admin/include/locale.php
+@@ -112,6 +112,21 @@ bindtextdomain($domain, $locale_dir);
+ bind_textdomain_codeset($domain, "UTF-8");
+ textdomain($domain);
+ 
++// Adjust session for language switch
++if(!empty($_REQUEST["lang"]))
++	foreach($_SESSION['customer_dn_options'] as $key => $opt)
++		if(!$opt['subtree'])
++			switch($opt['ignore']) {
++				case 0:
++					$_SESSION['customer_dn_options'][$key]['descr']
++							= _('No customer');
++					break;
++				case 1:
++					$_SESSION['customer_dn_options'][$key]['descr']
++							= _('Everything');
++					break;
++			}
++
+ /*
+   Local variables:
+   mode: php
+diff --git a/php/admin/include/menu.php b/php/admin/include/menu.php
+index 6954c5b..563fc79 100644
+--- a/php/admin/include/menu.php
++++ b/php/admin/include/menu.php
+@@ -41,7 +41,7 @@ if( $auth->group() == "admin" || $auth->group() == "maintainer" || $auth->group(
+ 												 array( 'name' => _('Vacation'),
+ 														'url'  => 'vacation.php' )));
+ }
+-if( $auth->group() == "admin" || $auth->group() == "maintainer") {
++if($auth->group() == "admin" || $auth->group() == "maintainer" || $auth->group() == "domain-maintainer") {
+   $menuitems['addressbook'] = array( 'name' => _('Addressbook'),
+ 									 'url'  => $topdir.'/addressbook/',
+ 									 'title' => _('Manage Address Book'),
+@@ -102,7 +102,7 @@ if( $auth->group() == 'admin' ) {
+ 									'url'  => $topdir.'/domainmaintainer/domainmaintainer.php?action=modify&dn='.urlencode($mdn),
+ 									'title' => _('Manage Domain Maintainer') );  
+ }
+-if( $auth->group() == 'admin' ) {
++if( $auth->group() == 'admin' || $auth->group() == 'maintainer' ) {
+   $menuitems['service'] = array( 'name' => _('Settings'),
+ 								 'url'  => $topdir.'/settings/',
+ 								 'title' => _('System Settings') );
+diff --git a/php/admin/include/mysmarty.php.in b/php/admin/include/mysmarty.php.in
+index 9762af8..ac2c4de 100644
+--- a/php/admin/include/mysmarty.php.in
++++ b/php/admin/include/mysmarty.php.in
+@@ -22,6 +22,22 @@ require_once('config.php');
+ require_once('@kolab_php_smarty_prefix@/Smarty.class.php');
+ require_once('locale.php');
+ 
++function switchToCustomer($idx) {
++	$cus = $_SESSION['customer_dn_options'];
++	if(is_array($cus) && $idx < count($cus)) {
++		$cus = $cus[$idx];
++		$_SESSION['customer_dn'] = $cus['dn'];
++		$_SESSION['customer_descr'] = $cus['descr'];
++		$_SESSION['customer_subtree'] = $cus['subtree'];
++		$_SESSION['ignore_customer'] = $cus['ignore'];
++	}
++}
++
++// Check if customer was changed
++$idx = $_REQUEST['customer_index'];
++if(is_numeric($idx))
++	switchToCustomer($idx);
++
+ // PENDING: Remove this before production(!)
+ //function count_bytes($tpl_output, &$smarty) {
+ //  return $tpl_output.strlen($tpl_output);
+@@ -53,6 +69,11 @@ class MySmarty extends Smarty {
+ 				   ($cleanurl.'?lang='):
+ 				   ($cleanurl.'&lang=') );
+ 	
++	$cleanurl = preg_replace('/(\?|&)customer_index=(.*)(&|$)/',
++			'', $_SERVER['REQUEST_URI']);
++	$this->assign('customer_url', $cleanurl
++			. (strpos($cleanurl, '?') === false ? '?' : '&') . 'customer_index=');
++
+ 	// If you add a translation, 
+ 	// add the new language here
+ 	$this->assign( 'currentlang', $language );
+@@ -70,6 +91,21 @@ class MySmarty extends Smarty {
+ 									  array( 'name' => 'Español',
+ 											 'code' => 'es_ES' ),
+ 									  ));
++	if(isset($_SESSION['customer_dn_options'])) {
++		$opts = array();
++		$index = 0;
++		$cudnopt = $_SESSION['customer_dn_options'];
++		foreach($cudnopt as $cust) {
++			$opts[] = array('name' => $cust['descr'], 'code' => $index);
++			++$index;
++		}
++		$this->assign('customer_select', $opts);
++		$this->assign('currentcustomer', isset($_SESSION['customer_dn'])
++				? findCustomerDnInArray($cudnopt, $_SESSION['customer_dn'],
++				$_SESSION['ignore_customer']) : 0);
++	}
++	else
++		$this->assign('customers', array());
+   }
+ 
+ 
+@@ -79,6 +115,16 @@ class MySmarty extends Smarty {
+   }
+ };
+ 
++function findCustomerDnInArray($a, $dn, $i) {
++	$idx = 0;
++	foreach($a as $e)
++		if($e['dn'] == $dn && $e['ignore'] == $i)
++			return $index;
++		else
++			++$index;
++	return -1;
++}
++
+ /*
+   Local variables:
+   mode: php
+diff --git a/php/admin/locale/de/LC_MESSAGES/messages.po b/php/admin/locale/de/LC_MESSAGES/messages.po
+index 75b71a6..1cce184 100644
+--- a/php/admin/locale/de/LC_MESSAGES/messages.po
++++ b/php/admin/locale/de/LC_MESSAGES/messages.po
+@@ -2424,3 +2424,75 @@ msgstr "Passwörter stimmen nicht überein"
+ 
+ #~ msgid "LDAP Error: Could not remove old entry %s,%s: %s"
+ #~ msgstr "LDAP-Fehler: Konnte den alten Eintrag %s,%s nicht entfernen: %s"
++
++#: ../../../www/admin/user/user.php.in:797
++msgid "LDAP Error: could not add object %s to customer group: %s"
++msgstr "LDAP Fehler: Konnte Objekt %s nicht zur Kundengruppe hinzufügen: %s"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "Customer"
++msgstr "Kunde"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "No customer"
++msgstr "Kein Kunde"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "Customers this domain maintainer sould be able to manage"
++msgstr "Kunden, die dieser Domänenverwalter verwalten können soll"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "LDAP Error: Failed to delete internal customer group object %s: %s"
++msgstr "LDAP Fehler: Konnte internes Kundengruppen Objekt %s nicht löschen: %s"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "Error deleting customer root object: Customer CN is empty"
++msgstr "Fehler beim Löschen des Kunden Stamm-Objektes: Kunden CN ist leer"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "LDAP Error: Failed to delete customer root object %s: %s"
++msgstr "LDAP Fehler: Konnte Kunden Stamm-Objekt %s nicht löschen: %s"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "LDAP Error: failed to add new customer root object: %s"
++msgstr "LDAP Fehler: Konnte Kunden Stamm-Objekt nicht hinzufügen: %s"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "LDAP Error: failed to add customer external addressbook object: %s"
++msgstr "LDAP Fehler: Konnte externes Kunden Adressbuch-Objekt nicht hinzufügen: %s"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "LDAP Error: failed to add customer internal shared folder object: %s"
++msgstr "LDAP Fehler Konnte internes Kundenobjekt für Gemeinsame Ordner nicht hinzufügen: %s"
++
++#: ../../../www/admin/domainmaintainer/domainmaintainer.php
++msgid "LDAP Error: failed to add internal customer group object: %s"
++msgstr "LDAP Fehler: Konnte internes Kundengruppen-Objekt nicht hinzufügen: %s"
++
++#: tpl_messages.php
++msgid "Customers"
++msgstr "Kunden"
++
++#: tpl_messages.php
++msgid "Customer ID"
++msgstr "Kunden ID"
++
++#: tpl_messages.php
++msgid "Customer name"
++msgstr "Kundenname"
++
++#: tpl_messages.php
++msgid "Are you really sure to delete this customer?"
++msgstr "Sind Sie wirklich sicher, dass dieser Kunde gelöscht werden soll?"
++
++#: unknown
++msgid "Everything"
++msgstr "Alles"
++
++#: unknown
++msgid "LDAP Error: failed to modify customer object: %s"
++msgstr "LDAP-Fehler: Konnte Kunden-Objekt nicht abändern: %s"
++
++#: unknown
++msgid "Customer/Domain Specific Settings"
++msgstr "Kunden-/Domänenspezifische Einstellungen"
+diff --git a/php/admin/templates/distlistall.tpl b/php/admin/templates/distlistall.tpl
+index 9fad827..09e7714 100755
+--- a/php/admin/templates/distlistall.tpl
++++ b/php/admin/templates/distlistall.tpl
+@@ -10,11 +10,14 @@
+ 
+ <table class="contenttable" cellpadding="0" cellspacing="1px">
+ 	<tr class="contentrow">
+-	<th>{tr msg="Listname"}</th><th>{tr msg="Visibility"}</th><th colspan="2">{tr msg="Action"}</th>
++	<th>{tr msg="Listname"}</th>{if $showcustomer == true}<th>{tr msg="Customer"}</th>{/if}<th>{tr msg="Visibility"}</th><th colspan="2">{tr msg="Action"}</th>
+ 	</tr>
+ {section name=id loop=$entries}
+ 	<tr class="contentrow{cycle values="even,odd"}">
+ 	   <td class="contentcell">{$entries[id].cn|escape:"html"}</td>
++	{if $showcustomer == true }
++		<td class="actioncell" nowrap>{$entries[id].customer}</td>
++	{/if}
+ 	{if $entries[id].internal == true }
+ 	   <td class="actioncell">{tr msg="Internal"}</td>
+ 	{else}
+diff --git a/php/admin/templates/page.tpl.in b/php/admin/templates/page.tpl.in
+index b0128b6..2b5ef05 100755
+--- a/php/admin/templates/page.tpl.in
++++ b/php/admin/templates/page.tpl.in
+@@ -24,6 +24,12 @@ function changeLanguage(combobox) {ldelim}
+ 	val=combobox.options[combobox.selectedIndex].value;
+         if(val!="") window.location="{$lang_url}"+val;
+ {rdelim}
++
++function changeCustomer(combobox) {ldelim}
++	var val = combobox.options[combobox.selectedIndex].value;
++	if(val != '')
++		window.location = '{$customer_url}' + val;
++{rdelim}
+ -->
+ </script>
+ 
+@@ -40,6 +46,17 @@ function changeLanguage(combobox) {ldelim}
+ {tr msg="Not logged in"}
+ {/if}
+ <br/>
++{if count($customer_select) > 1}
++	<select name="custom" class="langcombo" onchange="changeCustomer(this);">
++	{section name=id loop=$customer_select}
++		{if $customer_select[id].code==$currentcustomer}
++			<option value="{$customer_select[id].code}" selected="selected">{$customer_select[id].name}</option>
++		{else}
++			<option value="{$customer_select[id].code}">{$customer_select[id].name}</option>
++		{/if}
++	{/section}
++	</select>
++{/if}
+ <select name="lang" class="langcombo" onchange="changeLanguage(this);">
+ {section name=id loop=$languages}
+ {if $languages[id].code==$currentlang}
+diff --git a/php/admin/templates/settings.tpl b/php/admin/templates/settings.tpl
+index 6d1dfcc..0037428 100755
+--- a/php/admin/templates/settings.tpl
++++ b/php/admin/templates/settings.tpl
+@@ -4,27 +4,9 @@
+   buffer-file-coding-system: utf-8
+   End:
+ *}
++<hr />
+ <h1>{tr msg="Kolab Server Settings"}</h1>
+-
+-{if count($systemaliasconf)>0 }
+-<a name="systemaliasconf"></a>
+-<h2>{tr msg="Administrative email addresses"}</h2>
+-<div class="contentsimple">
+-<p>{tr msg="You have not yet set up a receiving account for the administrative email addresses hostmaster at yourdomain.tld, postmaster at yourdomain.tld, MAILER-DAEMON at yourdomain.tld, abuse at yourdomain.tld and virusalert at yourdomain.tld. Enter the email address of a kolab account below and press the button to create a distribution list for each of those addresses. Later you can add or remove people from the lists like any other distribution list"}</p>
+-{section name=id loop=$systemaliasconf}
+-<div class="contentform">
+-<form id="systemalias_{$systemaliasconf[id].n}" method="post" action="">
+-<div>
+-{tr msg="Email address of account that should receive administrative mail for domain "}  {$systemaliasconf[id].domain|escape:html}:
+-<input type="text" name="systemaliasmail_{$systemaliasconf[id].n}" size="80"  value="{$systemaliasmail[id]|escape:"html"}" /><br/>
+-<div class="align_right"><input type="submit" name="submitsystemalias_{$systemaliasconf[id].n}" value="{tr msg="Create Distribution Lists"}" /></div>
+-</div>
+-</form>
+-</div>
+-{/section}
+-<br />
+-</div>
+-{/if}
++<hr />
+ 
+ <h2>{tr msg="Enable or Disable individual Services"}</h2>
+ <form id="serviceform" method="post" action="">
+@@ -122,31 +104,6 @@
+  </form>
+ </div>
+ <br />
+-<h2>{tr msg="Domains"}</h2>
+-<table class="contenttable" cellpadding="0" cellspacing="1px">
+-	<tr class="contentrow">
+-	<th>{tr msg="Domain"}</th><th>{tr msg="Action"}</th>
+-	</tr>
+-{section name=id loop=$postfixmydestination}
+-	<form method="post" action="">
+-	<tr class="contentrow{cycle values="even,odd"}">
+-	   <td class="contentcell">{$postfixmydestination[id]|escape:"html"}</td>
+-	   <td class="actioncell">{strip}
+-		<input type="hidden" name="adestination" value="{$postfixmydestination[id]}" />
+-		<input type="submit" name="deletedestination" value="{tr msg="Delete"}" />
+-           {/strip}</td>
+-	</tr>
+-	</form>
+-{/section}
+-	<form method="post" action="">
+-	<tr class="contentrow{cycle values="even,odd"}">
+-	   <td class="contentcell"> 
+-		<input type="text" size="60" name="adestination" />
+-           </td><td class="actioncell"><input type="submit" name="adddestination" value="{tr msg="Add"}" /></td>
+-	</tr>
+-	</form>
+-</table>
+-<br/>
+ <h2>{tr msg="Mail Filter Settings"}</h2>
+ <div class="contentform">
+ <form id="kolabfilterform" method="post" action="">
+@@ -203,3 +160,137 @@
+ 	</tr>
+ 	</form>
+ </table>
++
++<br /><br />
++<hr />
++<h1>{tr msg="Customer/Domain Specific Settings"}</h1>
++<hr />
++
++{if count($systemaliasconf)>0 }
++<a name="systemaliasconf"></a>
++<h2>{tr msg="Administrative email addresses"}</h2>
++<div class="contentsimple">
++<p>{tr msg="You have not yet set up a receiving account for the administrative email addresses hostmaster at yourdomain.tld, postmaster at yourdomain.tld, MAILER-DAEMON at yourdomain.tld, abuse at yourdomain.tld and virusalert at yourdomain.tld. Enter the email address of a kolab account below and press the button to create a distribution list for each of those addresses. Later you can add or remove people from the lists like any other distribution list"}</p>
++{section name=id loop=$systemaliasconf}
++<div class="contentform">
++<form id="systemalias_{$systemaliasconf[id].n}" method="post" action="">
++<div>
++{tr msg="Email address of account that should receive administrative mail for domain "}  {$systemaliasconf[id].domain|escape:html}:
++<input type="text" name="systemaliasmail_{$systemaliasconf[id].n}" size="80"  value="{$systemaliasmail[id]|escape:"html"}" /><br/>
++<div class="align_right"><input type="submit" name="submitsystemalias_{$systemaliasconf[id].n}" value="{tr msg="Create Distribution Lists"}" /></div>
++</div>
++</form>
++</div>
++{/section}
++<br />
++</div>
++{/if}
++
++<br />
++
++<h2>{tr msg="Domains"}</h2>
++<table class="contenttable" cellpadding="0" cellspacing="1px">
++	<tr class="contentrow">
++		<th>{tr msg="Domain"}</th>
++		<th>{tr msg="Customer"}</th>
++		<th>{tr msg="Action"}</th>
++	</tr>
++{section name=id loop=$postfixmydestination}
++	<form method="post" action="">
++	<tr class="contentrow{cycle values="even,odd"}">
++		<td class="contentcell">{$postfixmydestination[id]|escape:"html"}</td>
++		<form method="post" action="">
++		<td>
++			<select name="owner">
++				{if $domainowners[id]==''}
++					<option value="" selected="selected">{tr msg="No customer"}</option>
++				{else}
++					<option value="">{tr msg="No customer"}</option>
++				{/if}
++				{section name=cidx loop=$allcustomers}
++					{if $allcustomers[cidx].cns==$domainowners[id]}
++						<option value="{$allcustomers[cidx].cns}" selected="selected">{$allcustomers[cidx].descriptions|escape:"html"}</option>
++					{else}
++						<option value="{$allcustomers[cidx].cns}">{$allcustomers[cidx].descriptions|escape:"html"}</option>
++					{/if}
++				{/section}
++			</select>
++			<input type="submit" name="changequota" value="{tr msg="Change"}" />
++		</td>
++		<td class="actioncell">{strip}
++<input type="hidden" name="adestination" value="{$postfixmydestination[id]}" />
++<input type="submit" name="deletedestination" value="{tr msg="Delete"}" />
++{/strip}</td>
++	</tr>
++	</form>
++{/section}
++	<form method="post" action="">
++	<tr class="contentrow{cycle values="even,odd"}">
++		<td class="contentcell"> 
++			<input type="text" size="60" name="adestination" />
++		</td>
++		<td>
++			<select name="owner">
++				{if $selected_customer_cn==''}
++					<option value="" selected="selected">{tr msg="No customer"}</option>
++				{else}
++					<option value="">{tr msg="No customer"}</option>
++				{/if}
++				{section name=cidx loop=$allcustomers}
++					{if $allcustomers[cidx].cns==$selected_customer_cn}
++						<option value="{$allcustomers[cidx].cns}" selected="selected">{$allcustomers[cidx].descriptions|escape:"html"}</option>
++					{else}
++						<option value="{$allcustomers[cidx].cns}">{$allcustomers[cidx].descriptions|escape:"html"}</option>
++					{/if}
++				{/section}
++			</select>
++		</td>
++		<td class="actioncell">
++			<input type="submit" name="adddestination" value="{tr msg="Add"}" />
++		</td>
++	</tr>
++	</form>
++</table>
++<br/>
++
++<h2>{tr msg="Customers"}</h2>
++<table class="contenttable" cellpadding="0" cellspacing="1px">
++	<tr class="contentrow">
++		<th>{tr msg="Customer ID"}</th>
++		<th>{tr msg="Customer name"}</th>
++		<th>{tr msg="Action"}</th>
++	</tr>
++{section name=id loop=$customers}
++	<tr class="contentrow{cycle values="even,odd"}">
++		<td class="contentcell">{$customers[id].cn|escape:"html"}</td>
++		<form method="post" action="">
++		<td class="contentcell" nowrap>
++			<input type="text" name="description" size="25" value="{$customers[id].description}" />
++			<input type="hidden" name="customer_cn" value="{$customers[id].cn}" />
++			<input type="submit" name="changecustomer" value="{tr msg="Change"}" />
++		</td>
++		</form>
++		<td class="actioncell">{strip}
++			<form method="post" action="">
++				<input type="hidden" name="customer_cn" value="{$customers[id].cn}" />
++				<input type="submit" name="deletecustomer" value="{tr msg="Delete"}" onclick="
++					return confirm('{tr msg="Are you really sure to delete this customer?"}\n{$customers[id].description}');
++				" />
++			</form>
++		{/strip}</td>
++	</tr>
++{/section}
++	<form method="post" action="">
++		<tr class="contentrow{cycle values="even,odd"}">
++			<td class="contentcell">
++				<input type="text" size="30" name="customer_cn" />
++			</td>
++			<td class="contentcell">
++				<input type="text" size="25" name="description" />
++			</td>
++			<td class="actioncell">
++				<input type="submit" name="addcustomer" value="{tr msg="Add"}" />
++			</td>
++		</tr>
++	</form>
++</table>
+diff --git a/php/admin/templates/sflistall.tpl b/php/admin/templates/sflistall.tpl
+index 3958e18..f43bb08 100755
+--- a/php/admin/templates/sflistall.tpl
++++ b/php/admin/templates/sflistall.tpl
+@@ -10,11 +10,20 @@
+ 
+ <table class="contenttable" cellpadding="0" cellspacing="1px">
+ 	<tr class="contentrow">
+-	<th>{tr msg="Name"}</th><th>{tr msg="Server"}</th><th>{tr msg="Type"}</th><th colspan="2">{tr msg="Action"}</th>
++		<th>{tr msg="Name"}</th>
++		{if $showcustomer == true}
++			<th>{tr msg="Customer"}</th>
++		{/if}
++		<th>{tr msg="Server"}</th>
++		<th>{tr msg="Type"}</th>
++		<th colspan="2">{tr msg="Action"}</th>
+ 	</tr>
+ {section name=id loop=$entries}
+ 	<tr class="contentrow{cycle values="even,odd"}">
+ 	   <td class="contentcell">{$entries[id].cn|escape:"html"}</td>
++		{if $showcustomer == true}
++			<td class="contentcell">{$entries[id].customer}</td>
++		{/if}
+ 	   <td class="contentcell">{$entries[id].kolabhomeserver|escape:"html"}</td>
+ 	   <td class="contentcell">{$entries[id].foldertype|escape:"html"}</td>
+ 	{if $entries[id].deleted neq "FALSE"}
+diff --git a/www/admin/addressbook/addr.php.in b/www/admin/addressbook/addr.php.in
+index bb70ecf..77762e4 100644
+--- a/www/admin/addressbook/addr.php.in
++++ b/www/admin/addressbook/addr.php.in
+@@ -13,7 +13,8 @@ $sidx = 'addressbook';
+ $contenttemplate = 'formcontainer.tpl';
+ $valid_actions = array('firstsave','save','modify','create','delete','kill');
+ 
+-if( $auth->group() != 'maintainer' && $auth->group() != 'admin') {
++if( $auth->group() != 'maintainer' && $auth->group() != 'admin'
++		&& $auth->group() != 'domain-maintainer') {
+    array_push($errors, _("Error: You don't have Permissions to access this Menu") );
+ }
+ 
+@@ -77,7 +78,8 @@ if (!empty($_REQUEST['action']) &&
+     in_array($_REQUEST['action'],$valid_actions)) $action = trim($_REQUEST['action']);
+ else array_push($errors, _("Error: need valid action to proceed") );
+ 
+-if (!$errors && $auth->group() != 'maintainer' && $auth->group() != 'admin') 
++if (!$errors && $auth->group() != 'maintainer' && $auth->group() != 'admin'
++		&& $auth->group() != 'domain-maintainer') 
+ 	 array_push($errors, _("Error: You don't have the required Permissions") );
+ 
+ $attributes = array( 'title', 'cn', 'sn', 'mail', 'alias', 'o',
+@@ -133,13 +135,20 @@ if( !$errors ) {
+ 	break;
+   case 'firstsave':
+ 		$ldap_object = array('objectClass' => array( 'top', 'inetOrgPerson', 'kolabInetOrgPerson' ) );
++		if(!empty($_SESSION['customer_subtree'])) {
++			$customer_dn = $_SESSION['customer_dn'];
++			$customer_dn = substr($customer_dn, strlen($customer_dn) - strlen($_SESSION['base_dn']));
++		}
+   case 'save':	
+ 	if( $form->isSubmitted() ) {
+ 	  if( !$form->validate() ) {
+ 		$form->setValues();
+ 		$content = $form->outputForm();
+ 	  } else {
+-		$addressbook_root = "cn=external,".$_SESSION['base_dn'];   
++		if($action == 'firstsave')
++			$addressbook_root = 'cn=external,' . $_SESSION['customer_dn'];
++		else
++			$addressbook_root = preg_replace('/^cn=[^,]*,/', '', $_REQUEST['dn']);
+ 		$firstname = trim($_POST['firstname']);
+ 		$lastname = trim($_POST['lastname']);
+ 		$ldap_object['sn'] = trim($lastname);
+@@ -308,7 +317,7 @@ if( !$errors ) {
+ 	}
+ 	
+ 	if (!$errors) {    
+-	  if (!($ldap->deleteObject($dn))) {
++	  if (!($ldap->deleteObject($dn, false, false))) {
+ 		array_push($errors, sprintf(_("LDAP Error: could not delete %s: %s"),$dn,
+ 									ldap_error($ldap->connection)));
+ 	  } else {
+diff --git a/www/admin/addressbook/index.php.in b/www/admin/addressbook/index.php.in
+index 8c3e059..6fe0477 100644
+--- a/www/admin/addressbook/index.php.in
++++ b/www/admin/addressbook/index.php.in
+@@ -18,7 +18,8 @@ $errors = array();
+ /**** Authentication etc. ***/
+ $sidx = 'addressbook';
+ 
+-if( $auth->group() != 'maintainer' && $auth->group() != 'admin') {
++if( $auth->group() != 'maintainer' && $auth->group() != 'admin'
++		&& $auth->group() != 'domain-maintainer') {
+   debug("auth->group=".$auth->group());
+   array_push($errors, _("Error: You don't have Permissions to access this Menu"));
+ }
+@@ -39,7 +40,8 @@ else $page = "1";
+ // Get all entries & dynamically split the letters with growing entries
+ $entries = array();
+ if( !$errors ) {
+-  if (isset($_SESSION['base_dn'])) $base_dn = $_SESSION['base_dn'];
++  if (isset($_SESSION['customer_dn'])) $base_dn = $_SESSION['customer_dn'];
++  elseif (isset($_SESSION['base_dn'])) $base_dn = $_SESSION['base_dn'];
+   else $base_dn = 'k=kolab';
+   $userfilter = "cn=*";
+   $filterattr = KolabForm::getRequestVar('filterattr');
+@@ -75,7 +77,10 @@ if( !$errors ) {
+ 	}
+   }
+   $filter = "(&($userfilter)$alphalimit(objectclass=inetOrgPerson)(!(uid=*))(sn=*))";
+-  $result = ldap_search($ldap->connection, $base_dn, $filter);
++  if($_SESSION['ignore_customer'])
++	$result = ldap_search($ldap->connection, $_SESSION['base_dn'], $filter);
++  else
++	$result = ldap_list($ldap->connection, 'cn=external,' . $base_dn, $filter);
+   if( $result ) {
+ 	$count = ldap_count_entries($ldap->connection, $result);
+ 	$title = "Manage Address Book ($count Addresses)";
+diff --git a/www/admin/distributionlist/index.php.in b/www/admin/distributionlist/index.php.in
+index 73b5b92..27b7cfc 100644
+--- a/www/admin/distributionlist/index.php.in
++++ b/www/admin/distributionlist/index.php.in
+@@ -24,6 +24,7 @@ require_once('@kolab_php_module_prefix at admin/include/mysmarty.php');
+ require_once('@kolab_php_module_prefix at admin/include/headers.php');
+ require_once('@kolab_php_module_prefix at admin/include/locale.php');
+ require_once('@kolab_php_module_prefix at admin/include/authenticate.php');
++require_once('@kolab_php_module_prefix at admin/include/customer.php');
+ 
+ $errors = array();
+ 
+@@ -44,7 +45,8 @@ $menuitems[$sidx]['selected'] = 'selected';
+ 
+ // Get all entries & dynamically split the letters with growing entries
+ if( !$errors ) {
+-  if (isset($_SESSION['base_dn'])) $base_dn = $_SESSION['base_dn'];
++  if (isset($_SESSION['customer_dn'])) $base_dn = $_SESSION['customer_dn'];
++  elseif (isset($_SESSION['base_dn'])) $base_dn = $_SESSION['base_dn'];
+   else $base_dn = 'k=kolab';
+   $domains = $ldap->domainsForMaintainerDn($auth->dn());
+   if( is_array($domains) ) {
+@@ -56,8 +58,16 @@ if( !$errors ) {
+   } else {
+   	$domainfilter= "";
+   }
+-  $filter = "(&(!(cn=domains))$domainfilter(objectclass=kolabGroupOfNames))";
+-  $result = ldap_search($ldap->connection, $base_dn, $filter);
++  $filter = "(&(!(cn=domains))(!(cn=customers))$domainfilter(objectclass=kolabGroupOfNames))";
++  if($_SESSION['ignore_customer']) {
++	$result1 = ldap_search($ldap->connection, $_SESSION['base_dn'], $filter);
++	$result2 = FALSE;
++  }
++  else {
++	$result1 = ldap_list($ldap->connection, $base_dn, $filter);
++	$result2 = ldap_list($ldap->connection, "cn=internal,$base_dn", $filter);
++  }
++  foreach(array($result1, $result2) as $result)
+   if( $result ) {
+ 	$count = ldap_count_entries($ldap->connection, $result);
+ 	$title = sprintf( _("Manage Distribution Lists (%d Lists)"), $count);
+@@ -69,13 +79,21 @@ if( !$errors ) {
+ 	  $dn = ldap_get_dn($ldap->connection,$entry);
+ 	  $cn = $attrs['cn'][0];
+ 	  if( $cn != 'admin' && $cn != 'maintainer' && $cn != 'domain-maintainer' 
+-		  && !preg_match('/.*,cn=domains,cn=internal,'.$_SESSION['base_dn'].'/', $dn ) ) {
++		  && !preg_match('/.*,cn=domains,cn=internal,'.$_SESSION['base_dn'].'/', $dn )
++		  && !preg_match('/.*,cn=customers,cn=internal,'.$_SESSION['base_dn'].'/', $dn) ) {
+ 		$deleted = array_key_exists('kolabDeleteflag',$attrs)?$attrs['kolabDeleteflag'][0]:"FALSE";
+ 		$kolabhomeserver = _('not yet implemented');
+ 		$internal = (strpos($dn,"cn=internal")!==false);
++		if($auth->group() == "manager" || $auth->group() == "admin") {
++			$dldn = str_replace(",".$_SESSION["base_dn"],"",$dn);
++			$matches = preg_split("/,/",$dldn);
++			$dldn = $matches[count($matches)-1];
++			$customer = customerDnToDescription($dldn);
++		}
+ 		$entries[] = array( 'dn' => $dn,
+ 							'cn' => $cn,
+ 							'deleted' => $deleted,
++							'customer' => $customer,
+ 							'internal' => $internal );
+ 	  }
+ 	  $entry = ldap_next_entry( $ldap->connection,$entry );
+@@ -96,6 +114,8 @@ $smarty->assign( 'submenuitems',
+ 				 array_key_exists('submenu', 
+ 								  $menuitems[$sidx])?$menuitems[$sidx]['submenu']:array() );
+ $smarty->assign( 'maincontent', $template );
++if ($auth->group() == "manager" || $auth->group() == "admin")
++	$smarty->assign( 'showcustomer', "true" );
+ $smarty->display('page.tpl');
+ 
+ /*
+diff --git a/www/admin/distributionlist/list.php.in b/www/admin/distributionlist/list.php.in
+index 8b38c6b..a5cc147 100644
+--- a/www/admin/distributionlist/list.php.in
++++ b/www/admin/distributionlist/list.php.in
+@@ -65,9 +65,12 @@ function checkuniquemail( $form, $key, $value ) {
+   return '';	  
+ }
+ 
+-function domain_dn()
++function domain_dn($customer_dn = '')
+ {
+-  return $_SESSION['base_dn'];
++	if (isset($_SESSION['customer_dn']))
++		return $_SESSION['customer_dn'];
++	else
++		return $customer_dn . $_SESSION['base_dn'];
+ }
+ 
+ function fill_form_for_modify( &$form, &$ldap_object ) {
+@@ -110,9 +113,7 @@ if (!empty($_REQUEST['dn'])) $dn = trim($_REQUEST['dn']);
+ 
+ $entries = array( 'cn' => array( 'name' => _('List Name'),
+ 								 'type' => 'email',
+-								 'domains' => ($auth->group()=='domain-maintainer')
+-								 ?$ldap->domainsForMaintainerDn($auth->dn())
+-								 :$ldap->domains(),
++								 'domains' => $ldap->accessibleDomainsOfSelectedCustomer(),
+ 								 'validation' => 'notempty',
+ 								 'comment' => _('Required') ),
+ 				  'members' => array( 'name' => _('Members'),
+@@ -153,7 +154,10 @@ if( !$errors ) {
+ 		$form->setValues();
+ 		$content = $form->outputForm();
+ 	  } else {
+-		$dl_root = domain_dn();
++		if($action == 'firstsave')
++			$dl_root = $_SESSION['customer_dn'];
++		else
++			$dl_root = preg_replace('/^cn=[^,]*,(cn=internal,)?/', '', $_REQUEST['dn']);
+ 
+ 		if (!empty($_POST['hidden']) && $_POST['hidden'] == "on") 
+ 		  $visible = false;
+@@ -161,6 +165,8 @@ if( !$errors ) {
+ 		if (!$visible) $dl_root = "cn=internal,".$dl_root;
+ 
+ 		$ldap_object = array('objectClass' => 'kolabGroupOfNames');
++		if(!in_array($_POST['domain_cn'], $ldap->accessibleDomainsOfSelectedCustomer()))
++			$errors[] = _('Error: You don\'t have the required Permissions');
+ 		$cn = strtolower(trim( trim($_POST['user_cn']).'@'.$_POST['domain_cn']));
+ 
+ 		// Keep cn and mail in sync
+diff --git a/www/admin/domainmaintainer/domainmaintainer.php.in b/www/admin/domainmaintainer/domainmaintainer.php.in
+index 13d7e26..8ce971b 100644
+--- a/www/admin/domainmaintainer/domainmaintainer.php.in
++++ b/www/admin/domainmaintainer/domainmaintainer.php.in
+@@ -24,6 +24,7 @@ require_once('@kolab_php_module_prefix at admin/include/locale.php');
+ require_once('@kolab_php_module_prefix at admin/include/authenticate.php');
+ require_once('@kolab_php_module_prefix at admin/include/form.class.php');
+ require_once('@kolab_php_module_prefix at admin/include/passwd.php');
++require_once('@kolab_php_module_prefix at admin/include/customer.php');
+ 
+ /**** Functions ***/
+ function comment( $s ) {
+@@ -75,9 +76,21 @@ function fill_form_for_modify( &$form, &$ldap_object ) {
+ 	$form->entries['lastname']['attrs'] = 'readonly';
+   } else {
+     $form->entries['domains']['value'] = $ldap->domainsForMaintainerUid($uid);
++	$form->entries['customer_dn']['value'] =
++			$ldap->customersForMaintainerDn("cn=$cn,cn=internal," . $_SESSION['base_dn']);
+   }
+ }
+ 
++function CustomerUidPrefixForId($id) {
++	if ($id > 0) {
++		$customer_dn = "cn=" . getCustomerList(true, $id) .
++				",cn=customers,cn=internal," . $_SESSION['base_dn'];
++		return getPrefixForCustomer($customer_dn);
++	}
++	else
++		return false;
++}
++
+ /**** Authentication etc. ***/
+ $sidx = 'domain-maintainer';
+ 
+@@ -131,6 +144,14 @@ $entries = array( 'firstname' => array( 'name' => _('First Name'),
+ );
+ 
+ if( $auth->group() == 'admin' || $auth->group() == 'maintainer' ) {
++  $customer_list = getNamedCustomerList();
++  $entries['customer_dn'] = array(
++		'name' => _('Customer'),
++		'type' => 'checklist',
++		'comment' => _('Customers this domain maintainer sould be able to manage'),
++		'options' => $customer_list['cns'],
++		'names' => $customer_list['descriptions']
++  );
+   $entries['domains'] = array( 'name' => _("Domains"),
+ 							   'type' => "checklist",
+ 							   'validation' => 'notempty',
+@@ -220,6 +241,9 @@ switch( $action ) {
+ 				   $domains = $ldap->domainsForMaintainerDn($dn);
+ 				   $ldap->removeFromDomainGroups( $dn, $domains );
+ 				   $ldap->addToDomainGroups( $newdn, $_POST['domains'] );
++				   $customers = $ldap->customersForMaintainerDn($dn);
++				   $ldap->removeFromCustomerGroups($dn, $customers);
++				   $ldap->addToCustomerGroups($newdn, $_POST['customer_dn']);
+ 				 }
+ 			   }			   
+ 			   $dn = $newdn;
+@@ -232,10 +256,16 @@ switch( $action ) {
+ 			 }
+ 			 if( $auth->group() == 'admin' || $auth->group() == 'maintainer' ) {
+ 			   if( !$errors ) {
+-				 $domains = $ldap->domainsForMaintainerDn($dn);
+-				 $remove_domains = array_diff($domains,$_POST['domains']);			   
+-				 $ldap->removeFromDomainGroups( $dn, $remove_domains );
+-			   $ldap->addToDomainGroups( $newdn, $_POST['domains'] );
++					$domains = $ldap->domainsForMaintainerDn($newdn);
++					$post_doms = is_array($_POST['domains']) ? $_POST['domains'] : array();
++					$remove_domains = array_diff($domains, $post_doms);
++					$ldap->removeFromDomainGroups( $newdn, $remove_domains );
++					$ldap->addToDomainGroups($newdn, $post_doms);
++					$customers = $ldap->customersForMaintainerDn($newdn);
++					$post_cust = is_array($_POST['customer_dn']) ? $_POST['customer_dn'] : array();
++					$remove_customers = array_diff($customers, $post_cust);
++					$ldap->removeFromCustomerGroups($dn, $remove_customers);
++					$ldap->addToCustomerGroups($newdn, $post_cust);
+ 			   }
+ 			 }
+ 		   }
+@@ -267,6 +297,7 @@ switch( $action ) {
+ 		   }
+ 		   if( $dn && !$errors ) {
+ 			   $ldap->addToDomainGroups( $dn, $_POST['domains'] );			 
++			   $ldap->addToCustomerGroups($dn, $_POST['customer_dn']);
+ 		   }
+ 		   if( !$errors ) {
+ 			 $messages[] = _('Domain maintainer ').$ldap_object['dn']._(' successfully created');
+diff --git a/www/admin/settings/index.php.in b/www/admin/settings/index.php.in
+index 892da7f..72e8536 100644
+--- a/www/admin/settings/index.php.in
++++ b/www/admin/settings/index.php.in
+@@ -3,14 +3,16 @@ require_once('@kolab_php_module_prefix at admin/include/mysmarty.php');
+ require_once('@kolab_php_module_prefix at admin/include/headers.php');
+ require_once('@kolab_php_module_prefix at admin/include/locale.php');
+ require_once('@kolab_php_module_prefix at admin/include/authenticate.php');
++require_once('@kolab_php_module_prefix at admin/include/customer.php');
+ 
+ $errors = array();
+ 
+ /**** Authentication etc. ***/
+ $sidx = 'service';
+ 
+-if( $auth->group() != 'admin') {
++if( $auth->group() != 'admin' && $auth->group() != 'maintainer' ) {
+    array_push($errors, _("Error: You don't have Permissions to access this Menu"));
++   $access_denied = true;
+ }
+ 
+ require_once('@kolab_php_module_prefix at admin/include/menu.php');
+@@ -27,6 +29,49 @@ function postvalue( $varname )
+   else return 'FALSE';
+ }
+ 
++function getCustomers() {
++	global $ldap;
++	if($_SESSION['customer_dn'] == $_SESSION['base_dn']
++			&& !$_SESSION['ignore_customer'])
++		return array();
++	$filter = '';
++	if($_SESSION['customer_dn'] != $_SESSION['base_dn'])
++		$filter = '(cn=' . canonicalizeEntity($_SESSION['customer_dn']) . ')';
++	if(($result = $ldap->search( "cn=customers,cn=internal," .
++			$_SESSION['base_dn'], "(&(objectClass=kolabGroupOfNames)$filter)",
++			array(
++				'cn',
++				'description'
++			)))) {
++		ldap_sort($ldap->connection,$result,'cn');
++		foreach($ldap->getEntries() as $v)
++			if(is_array($v))
++				$customer[] = array(
++					"cn" => $v["cn"][0],
++					"description" => $v["description"][0],
++				);
++		return $customer;
++	}
++	else
++		return false;
++}
++
++function ldap_delete_recursive($ds, $dn, $recursive = false) {
++	if(!$recursive)
++		return ldap_delete($ds,$dn);
++	//search for sub entries
++	$sr = ldap_list($ds, $dn, "ObjectClass=*", array(""));
++	$info = ldap_get_entries($ds, $sr);
++	for($i = 0; $i < $info['count']; $i++) {
++		// delete sub entries recursively
++		$result = ldap_delete_recursive($ds, $info[$i]['dn'], $recursive);
++		if(!$result)
++			// return result code if delete fails
++			return $result;
++	}
++	return ldap_delete($ds,$dn);
++}
++
+ function extract_ldap_values()
+ {
+   global $ldap;
+@@ -42,6 +87,7 @@ function extract_ldap_values()
+   global $freebusypast;
+   global $postfixmydomain;
+   global $postfixmydestination;
++  global $customers;
+   global $postfixmynetworks;
+   global $postfixallowunauth;
+   global $postfixrelayhost;
+@@ -68,6 +114,7 @@ function extract_ldap_values()
+ 	$postfixmydomain = $attrs['postfix-mydomain'][0];
+ 	$postfixmydestination = $attrs['postfix-mydestination'];
+ 	unset($postfixmydestination['count']);
++	$customers = getCustomers();
+ 	unset( $attrs['postfix-mynetworks']['count'] );
+ 	$postfixmynetworks = join(', ',$attrs['postfix-mynetworks']);
+ 	$postfixallowunauth = $attrs['postfix-allow-unauthenticated'][0];
+@@ -87,7 +134,7 @@ function toboolstr( $b ) { return ( $b == 'TRUE' )?'true':'false'; }
+ 
+ extract_ldap_values();
+ 
+-$domains = $ldap->domains();
++$domains = $ldap->domainsOfSelectedCustomer();
+ $domain_count = 0;
+ foreach( $domains as $domain ) {
+   // Write back to LDAP
+@@ -103,7 +150,12 @@ foreach( $domains as $domain ) {
+ 						'cn' => $gadr,
+ 						'mail' => $gadr,
+ 						'member' => $dn );
+-		if( !ldap_add( $ldap->connection, "cn=$gadr,".$_SESSION['base_dn'], $attrs ) ) {
++		$domainOwner = $ldap->customerOfDomain($domain);
++		if($domainOwner)
++			$aliasSubtree = "cn=$domainOwner," . $_SESSION['base_dn'];
++		else
++			$aliasSubtree = $_SESSION['base_dn'];
++		if(!ldap_add($ldap->connection, "cn=$gadr," . $aliasSubtree, $attrs)) {
+ 		  $errors[] = sprintf(_("LDAP Error: Failed to add distribution list %s: %s"), $gadr, $ldap->error());
+ 		} else {
+ 		  $messages[] = sprintf( _("Successfully created distribution list %s"), $gadr);
+@@ -203,6 +255,21 @@ if( $_REQUEST['submitpostfixrelayhost'] ) {
+   }
+ }
+ 
++// Change domain constraints
++if($_REQUEST['changequota']) {
++	extract_ldap_values();
++	$domainname = trim($_REQUEST['adestination']);
++	// Update LDAP
++	if(!$errors && is_array($attrs))
++		if(!($result = ldap_modify($ldap->connection, "cn=" . $ldap->escape($domainname)
++				. ",cn=domains,cn=internal," . $_SESSION['base_dn'], $attrs)))
++			$errors[] = sprintf(_("LDAP Error: failed to modify domain quotas: %s"),
++					ldap_error($ldap->connection));
++	if(!$errors)
++		if($result = setDomainOwner($_REQUEST['adestination'], $_REQUEST['owner']))
++			$errors[] = $result;
++}
++
+ // Delete domain
+ if( $_REQUEST['deletedestination'] ) {
+   extract_ldap_values();
+@@ -225,21 +292,112 @@ if( $_REQUEST['deletedestination'] ) {
+ 	$errors[] = sprintf(_("LDAP Error: Failed to delete domain object %s: %s"), $domain_obj_dn,
+ 						ldap_error($ldap->connection));
+   }
++  if($result = removeDomainFromAllCustomers($_REQUEST['adestination']))
++	$errors[] = $result;
+ }
+ // Add domain
+ if( $_REQUEST['adddestination'] ) {
+   extract_ldap_values();
+   if( trim($_REQUEST['adestination']) ) {
+-	$postfixmydestination[] = trim($_REQUEST['adestination']);
++	$postfixmydestination[] = $domainname = trim($_REQUEST['adestination']);
+ 	$attrs = array();
+ 	$attrs['postfix-mydestination'] = $postfixmydestination;
+ 	if( !($result = ldap_modify($ldap->connection, "k=kolab,".$_SESSION['base_dn'], $attrs)) ) {
+ 	  $errors[] = sprintf(_("LDAP Error: failed to modify kolab configuration object: %s"),
+ 						  ldap_error($ldap->connection));
+ 	}
++	if(empty($errors))
++		if($result = addDomainToCustomer($_REQUEST['adestination'], $_REQUEST['owner']))
++			$errors[] = $result;
+   }
+ }
+ 
++// Delete customer
++if($_REQUEST['deletecustomer']) {
++	$customer_obj_dn = 'cn=' . $ldap->escape($customer_cn = trim($_REQUEST['customer_cn']))
++			. ',cn=customers,cn=internal,' . $_SESSION['base_dn'];
++	if(!$errors && $ldap->read($customer_obj_dn) && !ldap_delete($ldap->connection, $customer_obj_dn))
++		$errors[] = sprintf(_("LDAP Error: Failed to delete internal customer group object %s: %s"),
++				$customer_obj_dn, ldap_error($ldap->connection));
++	else {
++		// Delete customer root object recursively
++		$customer_obj_dn = 'cn=' . $ldap->escape(trim($_REQUEST['customer_cn']))
++				. ',' . $_SESSION['base_dn'];
++		if(!trim($_REQUEST['customer_cn']))
++			$errors[] = _("Error deleting customer root object: Customer CN is empty");
++		if(!$errors && $ldap->read($customer_obj_dn) && !ldap_delete_recursive($ldap->connection,
++				$customer_obj_dn, true))
++			$errors[] = sprintf(_("LDAP Error: Failed to delete customer root object %s: %s"),
++					$customer_obj_dn, ldap_error($ldap->connection));
++	}
++	if(empty($errors))
++		adjustSessionForCustomerUpdate($customer_cn, 'removed');
++}
++
++// Add customer
++if($_REQUEST['addcustomer']) {
++	$customer_description = trim( $_REQUEST['description'] );
++	$customer_cn = trim($_REQUEST['customer_cn']);
++	$attrs = array();
++	$attrs['objectClass'] = array('top', 'kolabNamedObject');
++	$attrs['cn'] = $customer_cn;
++	// Create customer root object
++	if(!($result = ldap_add($ldap->connection, "cn=" . $customer_cn . ","
++			. $_SESSION['base_dn'], $attrs)))
++		$errors[] = sprintf(_("LDAP Error: failed to add new customer root object: %s"),
++				ldap_error($ldap->connection));
++	else {
++		// Create external addressbook context
++		$attrs['cn'] = "external";
++		if(!($result = ldap_add($ldap->connection, "cn=external,cn=" . $customer_cn
++				. ',' . $_SESSION['base_dn'], $attrs)))
++			$errors[] = sprintf(_("LDAP Error: failed to add customer external "
++					. "addressbook object: %s"), ldap_error($ldap->connection));
++		else {
++			// Create internal shared folder context
++			$attrs['cn'] = "internal";
++			if(!($result = ldap_add($ldap->connection, "cn=internal,cn="
++					. $customer_cn . ',' . $_SESSION['base_dn'], $attrs)))
++				$errors[] = sprintf(_("LDAP Error: failed to add customer internal "
++						. "shared folder object: %s"), ldap_error($ldap->connection));
++			else {
++				// Create internal customer group object
++				$attrs['cn'] = $customer_cn;
++				$attrs['description'] = $customer_description;
++				$attrs['member'] = "";
++				$attrs['objectClass'] = array('top', 'kolabGroupOfNames');
++				if(!($result = ldap_add($ldap->connection, "cn=" . $customer_cn
++						. ",cn=customers,cn=internal," . $_SESSION['base_dn'],
++						$attrs)))
++					$errors[] = sprintf(_("LDAP Error: failed to add internal "
++							. "customer group object: %s"),
++							ldap_error($ldap->connection));
++			}
++		}
++	}
++	if(empty($errors))
++		adjustSessionForCustomerUpdate($customer_cn, 'added', $customer_description,
++				$customer_kolabhomeserver, isset($customer_disablegroupware)
++				&& $customer_disablegroupware == 'TRUE');
++}
++
++// Change customer
++if($_REQUEST['changecustomer']) {
++	$customer_description = trim($_REQUEST['description']);
++	$customer_cn = trim($_REQUEST['customer_cn']);
++	if($customer_description == '')
++		$customer_description = array();
++	$attrs = array();
++	$attrs['description'] = $customer_description;
++	if(empty($errors))
++		if(!($result = ldap_modify($ldap->connection, "cn=" . $customer_cn
++				. ",cn=customers,cn=internal," . $_SESSION['base_dn'], $attrs)))
++			$errors[] = sprintf(_("LDAP Error: failed to modify customer object: %s"),
++					ldap_error($ldap->connection));
++	if(empty($errors))
++		adjustSessionForCustomerUpdate($customer_cn, 'modified', $customer_description);
++}
++
+ // Delete kolabhost
+ if( $_REQUEST['deletekolabhost'] ) {
+   extract_ldap_values();
+@@ -289,36 +447,47 @@ function exists_group( $group ) {
+   return ( $ldap->count($res) > 0 );
+ }
+ 
++function _customerOfDomain($d) {
++	global $ldap;
++	return $ldap->customerOfDomain($d);
++}
++
+ /**** Insert into template and output ***/
+ $smarty = new MySmarty();
+-$smarty->assign( 'errors', $errors );
+-$smarty->assign( 'uid', $auth->uid() );
+-$smarty->assign( 'group', $auth->group() );
+-$smarty->assign( 'page_title', $menuitems[$sidx]['title'] );
+-$smarty->assign( 'entries', $entries );
+-$smarty->assign( 'quotawarn', $quotawarn );
+-$smarty->assign( 'httpallowunauthfb', toboolstr($httpallowunauthfb) );
+-$smarty->assign( 'freebusypast', $freebusypast );
+-$smarty->assign( 'postfixmydestination', $postfixmydestination );
+-$smarty->assign( 'postfixmynetworks', $postfixmynetworks );
+-$smarty->assign( 'postfixallowunauth', toboolstr($postfixallowunauth) );
+-$smarty->assign( 'postfixrelayhost', $postfixrelayhost );
+-$smarty->assign( 'postfixrelayport', $postfixrelayport );
+-$smarty->assign( 'kolabfilterverifyfrom', toboolstr($kolabfilterverifyfrom) );
+-$smarty->assign( 'kolabfilterallowsender', toboolstr($kolabfilterallowsender) );
+-$smarty->assign( 'kolabfilterrejectforgedfrom', toboolstr($kolabfilterrejectforgedfrom) );
+-$smarty->assign( 'kolabhost', $kolabhost );
+-$smarty->assign( 'menuitems', $menuitems );
+-$smarty->assign( 'submenuitems', 
+-				 array_key_exists('submenu', 
+-								  $menuitems[$sidx])?$menuitems[$sidx]['submenu']:array() );
+-$smarty->assign( 'maincontent', 'settings.tpl' );
++if(!$access_denied) {
++	$smarty->assign( 'errors', $errors );
++	$smarty->assign( 'uid', $auth->uid() );
++	$smarty->assign( 'group', $auth->group() );
++	$smarty->assign( 'page_title', $menuitems[$sidx]['title'] );
++	$smarty->assign( 'entries', $entries );
++	$smarty->assign( 'quotawarn', $quotawarn );
++	$smarty->assign( 'httpallowunauthfb', toboolstr($httpallowunauthfb) );
++	$smarty->assign( 'freebusypast', $freebusypast );
++	$smarty->assign( 'postfixmydestination', $domains = $ldap->domainsOfSelectedCustomer() );
++	$smarty->assign( 'domainowners', array_map('_customerOfDomain', $domains));
++	$smarty->assign( 'allcustomers', arrayInsideOut(getNamedCustomerList()));
++	$smarty->assign( 'selected_customer_cn', getSelectedCustomerCN());
++	$smarty->assign( 'customers', $customers );
++	$smarty->assign( 'postfixmynetworks', $postfixmynetworks );
++	$smarty->assign( 'postfixallowunauth', toboolstr($postfixallowunauth) );
++	$smarty->assign( 'postfixrelayhost', $postfixrelayhost );
++	$smarty->assign( 'postfixrelayport', $postfixrelayport );
++	$smarty->assign( 'kolabfilterverifyfrom', toboolstr($kolabfilterverifyfrom) );
++	$smarty->assign( 'kolabfilterallowsender', toboolstr($kolabfilterallowsender) );
++	$smarty->assign( 'kolabfilterrejectforgedfrom', toboolstr($kolabfilterrejectforgedfrom) );
++	$smarty->assign( 'kolabhost', $kolabhost );
++	$smarty->assign( 'menuitems', $menuitems );
++	$smarty->assign( 'submenuitems',
++					 array_key_exists('submenu',
++									  $menuitems[$sidx])?$menuitems[$sidx]['submenu']:array() );
++	$smarty->assign( 'maincontent', 'settings.tpl' );
++}
+ 
+ $systemaliasconf = array();
+ 
+ if( $auth->group() == 'admin' ) {
+   $domain_count = 0;
+-  foreach( $ldap->domains() as $domain ) {
++  foreach( $ldap->domainsOfSelectedCustomer() as $domain ) {
+ 	if( !exists_group( 'hostmaster@'.$domain ) ||
+ 		!exists_group( 'postmaster@'.$domain ) ||
+ 		!exists_group( 'abuse@'.$domain ) ||
+diff --git a/www/admin/sharedfolder/index.php.in b/www/admin/sharedfolder/index.php.in
+index c8d105f..97c6e9d 100644
+--- a/www/admin/sharedfolder/index.php.in
++++ b/www/admin/sharedfolder/index.php.in
+@@ -11,6 +11,7 @@ require_once('@kolab_php_module_prefix at admin/include/mysmarty.php');
+ require_once('@kolab_php_module_prefix at admin/include/headers.php');
+ require_once('@kolab_php_module_prefix at admin/include/locale.php');
+ require_once('@kolab_php_module_prefix at admin/include/authenticate.php');
++require_once('@kolab_php_module_prefix at admin/include/customer.php');
+ 
+ $errors = array();
+ 
+@@ -36,7 +37,8 @@ function prepare_domain_filter_component($str) {
+ // Get all entries & dynamically split the letters with growing entries
+ $entries = array();
+ if( !$errors ) {
+-  if (isset($_SESSION['base_dn'])) $base_dn = $_SESSION['base_dn'];
++  if (isset($_SESSION['customer_dn'])) $base_dn = $_SESSION['customer_dn'];
++  elseif (isset($_SESSION['base_dn'])) $base_dn = $_SESSION['base_dn'];
+   else $base_dn = 'k=kolab';
+   if( $group == 'domain-maintainer' ) {
+ 	$domainfilter = '(|'.join('', array_map( 'prepare_domain_filter_component', 
+@@ -47,7 +49,10 @@ if( !$errors ) {
+   }
+   debug("domainfilter=$domainfilter");
+   $filter = "(&$domainfilter(objectclass=kolabSharedFolder))";
+-  $result = ldap_search($ldap->connection, $base_dn, $filter);
++  if($_SESSION['ignore_customer'])
++	$result = ldap_search($ldap->connection, $_SESSION['base_dn'], $filter);
++  else
++	$result = ldap_list($ldap->connection, $base_dn, $filter);
+   if( $result ) {
+ 	$count = ldap_count_entries($ldap->connection, $result);
+ 	$title = sprintf(_("Manage Shared Folders (%d Folders)"), $count);
+@@ -57,6 +62,14 @@ if( !$errors ) {
+ 	  while( $entry ) {
+ 		$attrs = ldap_get_attributes($ldap->connection, $entry);
+ 		$dn = ldap_get_dn($ldap->connection,$entry);
++		$df_dn = str_replace("," . $_SESSION['base_dn'], "", $dn);
++		if($auth->group() == "manager" || $auth->group() == "admin")
++			if(preg_match("/,/", $df_dn)) {
++				$df_customer = explode(",", $df_dn);
++				$customer = customerDnToDescription($df_customer[1]);
++			}
++			else
++				$customer = _("No customer");
+ 		$deleted = array_key_exists('kolabDeleteflag',$attrs)?$attrs['kolabDeleteflag'][0]:"FALSE";
+ 		$cn = $attrs['cn'][0];
+ 		$kolabhomeserver = $attrs['kolabHomeServer'][0];
+@@ -74,6 +87,7 @@ if( !$errors ) {
+ 							  'cn' => $cn,
+ 							  'kolabhomeserver' => $kolabhomeserver,
+ 							  'foldertype' => $folderType,
++							  'customer' => $customer,
+ 							  'deleted' => $deleted );
+ 		$entry = ldap_next_entry( $ldap->connection,$entry );
+ 	}
+@@ -92,6 +106,8 @@ $smarty->assign( 'submenuitems',
+ 				 array_key_exists('submenu', 
+ 								  $menuitems[$sidx])?$menuitems[$sidx]['submenu']:array() );
+ $smarty->assign( 'maincontent', $template );
++if($auth->group() == "manager" || $auth->group() == "admin")
++	$smarty->assign('showcustomer', "true");
+ $smarty->display('page.tpl');
+ 
+ /*
+diff --git a/www/admin/sharedfolder/sf.php.in b/www/admin/sharedfolder/sf.php.in
+index 38757dc..4d19b45 100644
+--- a/www/admin/sharedfolder/sf.php.in
++++ b/www/admin/sharedfolder/sf.php.in
+@@ -12,6 +12,7 @@ require_once('@kolab_php_module_prefix at admin/include/headers.php');
+ require_once('@kolab_php_module_prefix at admin/include/locale.php');
+ require_once('@kolab_php_module_prefix at admin/include/authenticate.php');
+ require_once('@kolab_php_module_prefix at admin/include/form.class.php');
++require_once('@kolab_php_module_prefix at admin/include/customer.php');
+ 
+ /**** Authentication etc. ***/
+ $errors = array();
+@@ -81,7 +82,11 @@ function process_acl( $uid, $perm )
+ 	// Special users allowed
+ 	return "$uid $perm";
+   }
+-  $res = $ldap->search( $_SESSION['base_dn'], '(&(|(uid='.$ldap->escape($uid).')(mail='.$ldap->escape($uid).')(alias='.$ldap->escape($uid).'))(objectClass=kolabInetOrgPerson))', 
++  if(isset($_SESSION['customer_dn']))
++	$base_dn = $_SESSION['customer_dn'];
++  else
++	$base_dn = $_SESSION['base_dn'];
++  $res = $ldap->search( $base_dn, '(&(|(uid='.$ldap->escape($uid).')(mail='.$ldap->escape($uid).')(alias='.$ldap->escape($uid).'))(objectClass=kolabInetOrgPerson))',
+ 						array('dn', 'mail' ) );
+   if( $ldap->count($res) == 1 ) {
+ 	// Ok, we have a regular user
+@@ -91,7 +96,7 @@ function process_acl( $uid, $perm )
+ 	return "$mail $perm";
+   }
+   
+-  $res = $ldap->search( $_SESSION['base_dn'], '(&(cn='.$ldap->escape($uid).')(objectClass=kolabGroupOfNames))',
++  $res = $ldap->search( $base_dn, '(&(cn='.$ldap->escape($uid).')(objectClass=kolabGroupOfNames))',
+ 						array('dn') );
+   if( $ldap->count($res) == 1 ) {
+ 	// Ok, we have a group
+@@ -116,9 +121,7 @@ if (!empty($_REQUEST['dn'])) $dn = trim($_REQUEST['dn']);
+ 
+ $entries = array( 'cn' => array( 'name' => _('Folder Name'),
+ 								 'type' => 'email',
+-								 'domains' => ($auth->group()=='domain-maintainer')
+-								 ?$ldap->domainsForMaintainerDn($auth->dn())
+-								 :$ldap->domains(),
++								 'domains' => $ldap->accessibleDomainsOfSelectedCustomer(),
+ 								 'validation' => 'notempty',
+ 								 'comment' => _('Required') ),
+ 				  'kolabhomeserver' => array( 'name' => _('Folder Location'),
+@@ -165,8 +168,15 @@ if( !$errors ) {
+ 		$form->setValues();
+ 		$content = $form->outputForm();
+ 	  } else {
+-		$sf_root = $_SESSION['base_dn'];   
++		if($action == 'firstsave')
++			$sf_root = $_SESSION['customer_dn'];
++		else
++			$sf_root = preg_replace('/^cn=[^,]*,/', '', $_REQUEST['dn']);
+ 		$ldap_object = array('objectClass' => 'kolabSharedFolder');
++		// wtf? you don't check this? you don't check this...
++		// omg. i thought this was all for the BSI?
++		if(!in_array($_POST['domain_cn'], $ldap->accessibleDomainsOfSelectedCustomer()))
++			$errors[] = _('Error: You don\'t have the required Permissions');
+ 		// OK, we need to get the name down to lowercase ascii only
+ 		// we handle a few common cases here
+ 		// Really cheesy, but strtolower is latin1 only :-(
+diff --git a/www/admin/user/index.php.in b/www/admin/user/index.php.in
+index 7fe05af..6f3a39d 100644
+--- a/www/admin/user/index.php.in
++++ b/www/admin/user/index.php.in
+@@ -50,11 +50,12 @@ else $page = "1";
+ // Get all entries & dynamically split the letters with growing entries
+ $entries = array();
+ if( !$errors ) {
+-  if (isset($_SESSION['base_dn'])) $base_dn = $_SESSION['base_dn'];
++  if (isset($_SESSION['customer_dn'])) $base_dn = $_SESSION['customer_dn'];
++  elseif (isset($_SESSION['base_dn'])) $base_dn = $_SESSION['base_dn'];
+   else $base_dn = 'k=kolab';
+ 
+-  $privmembers = array_merge( (array)$ldap->groupMembers( "cn=internal,$base_dn", 'admin' ),
+-							  (array)$ldap->groupMembers( "cn=internal,$base_dn", 'maintainer' ) );
++  $privmembers = array_merge( (array)$ldap->groupMembers( "cn=internal," . $_SESSION['base_dn'], 'admin' ),
++							  (array)$ldap->groupMembers( "cn=internal," . $_SESSION['base_dn'], 'maintainer' ) );
+ 
+   $userfilter = "cn=*";
+   $filterattr = KolabForm::getRequestVar('filterattr');
+@@ -115,7 +116,17 @@ if( !$errors ) {
+   }
+   $filter = "(&($userfilter)$domainfilter$alphalimit(objectclass=kolabInetOrgPerson)(uid=*)(mail=*)(sn=*))";
+   debug("filter is \"$filter\"");
+-  $result = ldap_search($ldap->connection, $base_dn, $filter, array( 'uid', 'mail', 'sn', 'cn', 'kolabDeleteflag' ));
++  $attribs = array(
++	'uid',
++	'mail',
++	'sn',
++	'cn',
++	'kolabDeleteflag',
++  );
++  if($_SESSION['ignore_customer'])
++	$result = ldap_search($ldap->connection, $_SESSION['base_dn'], $filter, $attribs);
++  else
++	$result = ldap_list($ldap->connection, $base_dn, $filter, $attribs);
+ 
+   if( $result ) {
+ 	$count = ldap_count_entries($ldap->connection, $result);
+diff --git a/www/admin/user/user.php.in b/www/admin/user/user.php.in
+index 81d50d1..f2bf104 100644
+--- a/www/admin/user/user.php.in
++++ b/www/admin/user/user.php.in
+@@ -12,6 +12,7 @@ require_once('@kolab_php_module_prefix at admin/include/locale.php');
+ require_once('@kolab_php_module_prefix at admin/include/authenticate.php');
+ require_once('@kolab_php_module_prefix at admin/include/form.class.php');
+ require_once('@kolab_php_module_prefix at admin/include/passwd.php');
++require_once('@kolab_php_module_prefix at admin/include/customer.php');
+ 
+ /**** Functions ***/
+ function comment( $s ) {
+@@ -43,7 +44,10 @@ function domain_dn()
+   }
+   return $domain_dn;
+   */
+-  return $_SESSION['base_dn'];
++	if(isset($_SESSION['customer_dn']))
++		return $_SESSION['customer_dn'];
++	else
++		return $_SESSION['base_dn'];
+ }
+ 
+ // return tru if $str ends with $sub
+@@ -110,7 +114,7 @@ function checkuniquemail( $form, $key, $value ) {
+   }
+   if(!$ok) return sprintf(_("Email address %1\$s not in domains %2\$s"), $value, join(", ", $domain));
+ 
+-  if( $ldap->countMail( $_SESSION['base_dn'], $value ) > 0 ) {	
++  if( $ldap->countMail( $_SESSION['customer_dn'], $value ) > 0 ) {
+ 	return _('User, vCard or distribution list with this email address already exists');
+   } else {
+ 	return '';
+@@ -459,7 +463,7 @@ $entries = array( 'givenname' => array( 'name' => _('First Name'),
+ 					 'comment' => $comment_password ),
+ 		  'mail' => array( 'name' => _('Primary Email Address'),
+ 					 'type'       => 'email',
+-					 'domains'    => ($auth->group()=='domain-maintainer')?$ldap->domainsForMaintainerDn($auth->dn()):$ldap->domains(),
++					 'domains'    => $ldap->accessibleDomainsOfSelectedCustomer(),
+ 				     'validation' => 'notempty',
+ 				     'comment'    => $comment_mail ),
+ 		  'uid'    => array( 'name' => _('Unique Identity (UID)'),
+@@ -556,6 +560,8 @@ switch( $action ) {
+    $form->entries['mail']['validation'] = array( $form->entries['mail']['validation'], 'checkuniquemail');
+  case 'save':
+    if( $form->isSubmitted() ) {
++	 $mailaddress = trim(strtolower($_POST['user_mail'])) . '@'
++			. trim(strtolower($_POST['domain_mail']));
+      if( !$form->validate() ) {
+        $form->setValues();
+        $content = $form->outputForm();
+@@ -572,6 +578,8 @@ switch( $action ) {
+ 		   $auth->setPassword($_POST['password_0']);
+ 		 }
+        }
++       if(!in_array($_POST['domain_mail'], $ldap->accessibleDomainsOfSelectedCustomer()))
++			$errors[] = _('Error: You don\'t have the required Permissions');
+        $ldap_object['mail'] = trim( strtolower( $_POST['user_mail'] ) ).'@'.trim( strtolower( $_POST['domain_mail'] ) );
+        $ldap_object['uid'] = trim( strtolower( $_POST['uid'] ) );
+        if( $action == 'firstsave' ) {
+@@ -616,7 +624,10 @@ switch( $action ) {
+ 		   }
+ 		 }		 
+ 	   }
+-	   $dn_add = "";
++	   if(isset($_SESSION["customer_dn"]))
++			$dn_add = $_SESSION["customer_dn"];
++	   else
++			$dn_add = "";
+ 
+ 	   // kolabdelegate
+ 	   $ldap_object['kolabDelegate'] = array_unique( array_filter( array_map( 'trim', 
+@@ -629,7 +640,8 @@ switch( $action ) {
+ 	   if( !$ldap_object['kolabAllowSMTPRecipient'] && $action == 'firstsave' ) unset($ldap_object['kolabAllowSMTPRecipient']);
+ 
+ 
+-       if ($auth->group() == "maintainer" || $auth->group() == "admin") {
++       if ($auth->group() == "maintainer" || $auth->group() == "admin"
++			|| $auth->group() == "domain-maintainer") {
+ 		 // alias
+ 		 $ldap_object['alias'] = array_unique( array_filter( array_map( 'trim', preg_split( '/\n/', $_POST['alias'] ) ), 'strlen') );	   
+ 		 if( !$ldap_object['alias'] && $action == 'firstsave' ) unset($ldap_object['alias']);
+@@ -647,6 +659,9 @@ switch( $action ) {
+ 	   else if( $_POST['accttype'] == 2 ) $dn_accttype='cn=groups,';
+ 	   else if( $_POST['accttype'] == 3 ) $dn_accttype='cn=resources,';
+        $domain_dn = $dn_accttype.domain_dn();
++	   // Save correct subtree dsn in case of administrators edit
++	   if(preg_match("/,cn=(.)+" . $_SESSION['base_dn'] . "$/", $dn))
++			$domain_dn = "cn=" . preg_replace("/^cn=(.)+,cn=/", "", $dn);
+ 	   
+        if ($action == "save") {
+ 		 if (!$errors) {
+@@ -748,6 +763,8 @@ switch( $action ) {
+ 		 if( !$errors ) $messages[] = sprintf(_("User '%s' successfully modified"), $dn);
+ 		 $form->setValues();
+ 		 $form->entries['mail']['attrs'] = 'readonly';
++		 if($auth->group() != "domain-maintainer" && $auth->group() != "user")
++			$form->entries['kolabhomeserver']['attrs'] = 'readonly';
+ 		 $form->entries['kolabhomeserver']['attrs'] = 'readonly';
+ 		 $form->entries['action']['value'] = 'save';
+ 		 $form->entries['dn'] = array( 'name' => 'dn',
+@@ -757,14 +774,28 @@ switch( $action ) {
+        } else {
+ 		 // firstsave
+ 		 if (!$errors) {
+-		   $dn = "cn=".$ldap->dn_escape($ldap_object['cn']).$dn_add.",".$domain_dn;
++		   if($dn_add == "")
++				$dn_add = $domain_dn;
++		   $dn = "cn=" . $ldap->dn_escape($ldap_object['cn']) . "," . $dn_add;
+ 		   foreach( $ldap_object as $k => $v ) {
+ 			 if( $v == array() ) unset($ldap_object[$k]);
+ 		   }
+ 		   debug("Calling ldap_add with dn=$dn");
+-		   if ($dn && !ldap_add($ldap->connection, $dn, $ldap_object)) 
++		   // If a customer was selected change DN
++		   if ($dn && !ldap_add($ldap->connection, $dn, $ldap_object))
+ 			 array_push($errors, sprintf(_("LDAP Error: could not add object %s: %s"), $dn,
+ 										 ldap_error($ldap->connection)));
++		   elseif(!empty($_SESSION['customer_subtree']))
++			// Add user to selected customer group
++			$group_dn = $_SESSION['customer_subtree'];
++		   elseif(isset($_SESSION['customer_dn']))
++			// Add user to domain-maintainers customer group
++			$group_dn = $_SESSION['customer_subtree'];
++		   if(!empty($group_dn))
++			if($group_dn && !ldap_mod_add($ldap->connection, $group_dn, array('member' => $dn)))
++				array_push($errors,
++					sprintf(_("LDAP Error: could not add object %s to customer group: %s"),
++							$dn, ldap_error($ldap->connection)));
+ 
+ 		   // Check for mid-air collisions on mail
+ 		   if( $ldap->countMail( $_SESSION['base_dn'], $ldap_object['mail'], $dn ) > 0 ) {
+-- 
+1.7.3.2
+
diff --git a/debian/patches/0002-Add-quota-on-a-per-customer-basis.patch b/debian/patches/0002-Add-quota-on-a-per-customer-basis.patch
new file mode 100644
index 0000000..34eca9b
--- /dev/null
+++ b/debian/patches/0002-Add-quota-on-a-per-customer-basis.patch
@@ -0,0 +1,383 @@
+From a1593aeff34cce84d7fae14226eedd5cc47ccad2 Mon Sep 17 00:00:00 2001
+From: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
+Date: Tue, 9 Nov 2010 17:14:31 +0000
+Subject: [PATCH 2/5] Add quota on a per customer basis
+
+---
+ Makefile.am                                 |    1 +
+ kolab-webadmin.spec.in                      |    2 +-
+ php/admin/include/customer.php              |    2 +
+ php/admin/include/domainquota.php           |   98 +++++++++++++++++++++++++++
+ php/admin/locale/de/LC_MESSAGES/messages.po |   28 ++++++++
+ php/admin/templates/settings.tpl            |    7 ++
+ www/admin/settings/index.php.in             |   43 ++++++++++++
+ www/admin/user/user.php.in                  |   19 +++++
+ 8 files changed, 199 insertions(+), 1 deletions(-)
+ create mode 100644 php/admin/include/domainquota.php
+
+diff --git a/Makefile.am b/Makefile.am
+index 5df69aa..0c67c7d 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -113,6 +113,7 @@ PHP_INCLUDES = php/admin/include/menu.php \
+ 	php/admin/include/locale.php \
+ 	php/admin/include/Sieve.php \
+ 	php/admin/include/customer.php \
++	php/admin/include/domainquota.php \
+ 	php/admin/include/passwd.php
+ 
+ phpincludesdir = $(phpkolabdir)/include
+diff --git a/kolab-webadmin.spec.in b/kolab-webadmin.spec.in
+index 21b4a66..2670ba8 100644
+--- a/kolab-webadmin.spec.in
++++ b/kolab-webadmin.spec.in
+@@ -31,7 +31,7 @@ Distribution: OpenPKG
+ Group:        Mail
+ License:      GPL
+ Version:      @VERSION@
+-Release:      @spec_build_date at _hosted1
++Release:      @spec_build_date at _hosted2
+ 
+ 
+ #   list of sources
+diff --git a/php/admin/include/customer.php b/php/admin/include/customer.php
+index e99def4..d8bd3ae 100644
+--- a/php/admin/include/customer.php
++++ b/php/admin/include/customer.php
+@@ -302,6 +302,8 @@ function getCustomerUsedDomainQuota($customer) {
+ 	if(!$customer)
+ 		return 0;
+ 	$domains = $ldap->domainsOfCustomer($customer);
++	if(is_string($domains))
++		return $domains;
+ 	$used = 0;
+ 	foreach($domains as $domain)
+ 		$used += getCustomerUsedDomainQuotaByDomain($domain);
+diff --git a/php/admin/include/domainquota.php b/php/admin/include/domainquota.php
+new file mode 100644
+index 0000000..f114e9e
+--- /dev/null
++++ b/php/admin/include/domainquota.php
+@@ -0,0 +1,98 @@
++<?php
++/*
++ * Copyright (c) 2008 TBits.net GmbH
++ *
++ *    Written by Martin Zapfl <mz at tbits.net>
++ *
++ *  This  program is free  software; you can redistribute  it and/or
++ *  modify it  under the terms of the GNU  General Public License as
++ *  published by the  Free Software Foundation; either version 2, or
++ *  (at your option) any later version.
++ *
++ *  This program is  distributed in the hope that it will be useful,
++ *  but WITHOUT  ANY WARRANTY; without even the  implied warranty of
++ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++ *  General Public License for more details.
++ *
++ *  You can view the  GNU General Public License, online, at the GNU
++ *  Project's homepage; see <http://www.gnu.org/licenses/gpl.html>.
++ */
++
++// Get attribute for max. no. of accounts for a domain.
++function getMaxAccounts($domain){
++  global $ldap;
++  if ($ldap->search( "cn=".$domain.",cn=domains,cn=internal,".$_SESSION['base_dn'],"(objectClass=kolabGroupOfNames)",array("maxAccounts"))){
++    $maxaccounts = $ldap->getEntries();
++    if(is_array($maxaccounts)) return $maxaccounts[0]["maxaccounts"][0];
++    else return $maxaccounts;
++  } else return false;
++}
++
++// Count No. of accounts for a domain
++function countAccounts($domain, $excluded_accounts=""){
++  global $ldap;
++  if ("@".$domain == $excluded_accounts) $excluded_accounts = ""; // Avoid search match exclude pattern
++  if(is_array($excluded_accounts)){
++    $exclude_filter="(!";
++    foreach($excluded_accounts as $exclude_address){
++     $exclude_filter.="(mail=".$exclude_address.")";
++    }
++    $exclude_filter.=")";
++  } elseif ($excluded_accounts!="") $exclude_filter = "(!(mail=".$excluded_accounts."))";
++  if ($ldap->search( $_SESSION['base_dn'], "(&(objectClass=kolabInetOrgPerson)(mail=*".$domain.")".$exclude_filter.")", array("mail") )){
++    $accounts = $ldap->getEntries();
++    return $accounts["count"];
++  } else return false;
++}
++
++// Get domain quota space in use
++function getDomainQuotaInUse($domain){
++  global $ldap;
++  // Do not count userquota for current user
++  if (isset($_POST["user_mail"]) && isset($_POST["domain_mail"])){
++    $exclude_filter="(!(mail=".trim(strtolower($_POST["user_mail"]))."@".trim(strtolower($_POST["domain_mail"]))."))";
++  }
++  if ($ldap->search( $_SESSION['base_dn'], "(&(objectClass=kolabInetOrgPerson)(mail=*@".$domain.")".$exclude_filter.")",array("cyrus-userquota"))){
++    foreach($ldap->getEntries()as $v){
++      if(isset($v["cyrus-userquota" ])) $quotainuse =  $quotainuse + $v["cyrus-userquota"][0];
++    }
++    return $quotainuse;
++  } else return false;
++}
++
++// Get attribute for max. no. of accounts for a domain.
++function getDomainQuotas($domain){
++  global $ldap;
++  if ($ldap->search( "cn=".$domain.",cn=domains,cn=internal,".$_SESSION['base_dn'],"(objectClass=kolabGroupOfNames)",array("domainDefaultQuota","domainQuota"))){
++    foreach ($ldap->getEntries() as $v){
++      if(is_array($v)){
++        if(in_array('domainquota',$v,true)) $quota["domainquota"] = $v["domainquota"][0];
++        if(in_array("domaindefaultquota",$v,true)) $quota["domaindefaultquota"] = $v["domaindefaultquota"][0];
++      }
++    }
++    if (isset( $quota["domainquota"])) $quota["quotainuse"] = getDomainQuotaInUse($domain);
++    return $quota;
++  } else return false;
++}
++
++function getCustomerQuotaUsed($customer) {
++	global $ldap;
++	$domains = $ldap->domainsOfCustomer($customer);
++	if(!is_array($domains))
++		return $domains;
++	$used = 0;
++	foreach($domains as $domain)
++		$used += getDomainQuotaInUse($domain);
++	return $used;
++}
++
++function getUserQuotaByDN($user) {
++	global $ldap;
++	$attrs = $ldap->read($user);
++	if(!$attrs)
++		return 0;
++	$quota = $attrs['cyrus-userquota'][0];
++	return $quota ? $quota : 0;
++}
++
++?>
+diff --git a/php/admin/locale/de/LC_MESSAGES/messages.po b/php/admin/locale/de/LC_MESSAGES/messages.po
+index 1cce184..30cb2bc 100644
+--- a/php/admin/locale/de/LC_MESSAGES/messages.po
++++ b/php/admin/locale/de/LC_MESSAGES/messages.po
+@@ -2425,6 +2425,34 @@ msgstr "Passwörter stimmen nicht überein"
+ #~ msgid "LDAP Error: Could not remove old entry %s,%s: %s"
+ #~ msgstr "LDAP-Fehler: Konnte den alten Eintrag %s,%s nicht entfernen: %s"
+ 
++#: unknown
++msgid "Domain or customer quota for domain %s requires a default quota"
++msgstr "Domänen- oder Kundenkontingent für Domäne %s erfordert einen Wert für Standard Kontingent"
++
++#: unknown
++msgid "Domain quota may not exceed %d MBytes"
++msgstr "Domänenkontingent kann %d MBytes nicht überschreiten"
++
++#: unknown
++msgid "Mail quota for customer %s must be an integer"
++msgstr "E-Mail Kontingent für Kunden %s muss eine ganze Zahl sein"
++
++#: unknown
++msgid "Mail quota for customer %s cannot be set below %d MBytes, as this much is already being used"
++msgstr "E-Mail Kontingent für Kunden %s kann nicht kleiner als %d MBytes sein, da soviel bereits benutzt wird"
++
++#: unknown
++msgid "User quota must be an integer, not %s"
++msgstr "Benutzer-Kontingent muss eine ganze Zahl sein, nicht %s"
++
++#: unknown
++msgid "User Quota exceeds Customer Quota (%d MBytes left out of %d MBytes)"
++msgstr "Benutzer-Kontingent überschreitet Domänenkontingent (%d MBytes von %d MBytes übrig)"
++
++#: unknown
++msgid "No Quota left to create another user for domain %s"
++msgstr "Kein Kontingent mehr übrig, um einen weiteren Benutzer für Domäne %s zu erstellen"
++
+ #: ../../../www/admin/user/user.php.in:797
+ msgid "LDAP Error: could not add object %s to customer group: %s"
+ msgstr "LDAP Fehler: Konnte Objekt %s nicht zur Kundengruppe hinzufügen: %s"
+diff --git a/php/admin/templates/settings.tpl b/php/admin/templates/settings.tpl
+index 0037428..8bd2820 100755
+--- a/php/admin/templates/settings.tpl
++++ b/php/admin/templates/settings.tpl
+@@ -257,6 +257,7 @@
+ <table class="contenttable" cellpadding="0" cellspacing="1px">
+ 	<tr class="contentrow">
+ 		<th>{tr msg="Customer ID"}</th>
++		<th>{tr msg="Quota (MBytes)"}</th>
+ 		<th>{tr msg="Customer name"}</th>
+ 		<th>{tr msg="Action"}</th>
+ 	</tr>
+@@ -264,6 +265,9 @@
+ 	<tr class="contentrow{cycle values="even,odd"}">
+ 		<td class="contentcell">{$customers[id].cn|escape:"html"}</td>
+ 		<form method="post" action="">
++		<td class="contentcell">
++			<input type="text" name="customerquota" size="4" value="{if $customers[id].customerquota!='0'}{$customers[id].customerquota}{/if}" />
++		</td>
+ 		<td class="contentcell" nowrap>
+ 			<input type="text" name="description" size="25" value="{$customers[id].description}" />
+ 			<input type="hidden" name="customer_cn" value="{$customers[id].cn}" />
+@@ -286,6 +290,9 @@
+ 				<input type="text" size="30" name="customer_cn" />
+ 			</td>
+ 			<td class="contentcell">
++				<input type="text" name="customerquota" size="4" />
++			</td>
++			<td class="contentcell">
+ 				<input type="text" size="25" name="description" />
+ 			</td>
+ 			<td class="actioncell">
+diff --git a/www/admin/settings/index.php.in b/www/admin/settings/index.php.in
+index 72e8536..31227ad 100644
+--- a/www/admin/settings/index.php.in
++++ b/www/admin/settings/index.php.in
+@@ -4,6 +4,7 @@ require_once('@kolab_php_module_prefix at admin/include/headers.php');
+ require_once('@kolab_php_module_prefix at admin/include/locale.php');
+ require_once('@kolab_php_module_prefix at admin/include/authenticate.php');
+ require_once('@kolab_php_module_prefix at admin/include/customer.php');
++require_once('@kolab_php_module_prefix at admin/include/domainquota.php');
+ 
+ $errors = array();
+ 
+@@ -41,6 +42,7 @@ function getCustomers() {
+ 			$_SESSION['base_dn'], "(&(objectClass=kolabGroupOfNames)$filter)",
+ 			array(
+ 				'cn',
++				'customerQuota',
+ 				'description'
+ 			)))) {
+ 		ldap_sort($ldap->connection,$result,'cn');
+@@ -49,6 +51,8 @@ function getCustomers() {
+ 				$customer[] = array(
+ 					"cn" => $v["cn"][0],
+ 					"description" => $v["description"][0],
++					'customerquota' => $v['customerquota']['count'] ?
++							$v['customerquota'][0] : '',
+ 				);
+ 		return $customer;
+ 	}
+@@ -259,6 +263,12 @@ if( $_REQUEST['submitpostfixrelayhost'] ) {
+ if($_REQUEST['changequota']) {
+ 	extract_ldap_values();
+ 	$domainname = trim($_REQUEST['adestination']);
++	$ownerQuota = getCustomerQuota($_REQUEST['owner']);
++	$ownerUsed = getCustomerUsedDomainQuota($_REQUEST['owner'])
++			- getCustomerUsedDomainQuotaByDomain($domainname);
++	// Validate user input
++	//		hmm, i figure this requires stuff from the
++	//		domain-default-quota branch to process
+ 	// Update LDAP
+ 	if(!$errors && is_array($attrs))
+ 		if(!($result = ldap_modify($ldap->connection, "cn=" . $ldap->escape($domainname)
+@@ -300,12 +310,23 @@ if( $_REQUEST['adddestination'] ) {
+   extract_ldap_values();
+   if( trim($_REQUEST['adestination']) ) {
+ 	$postfixmydestination[] = $domainname = trim($_REQUEST['adestination']);
++	$ownerQuota = getCustomerQuota($_REQUEST['owner']);
++	$ownerUsed = getCustomerUsedDomainQuota($_REQUEST['owner']);
++	//		again, this requires stuff from the
++	//		domain-default-quota branch to process
+ 	$attrs = array();
+ 	$attrs['postfix-mydestination'] = $postfixmydestination;
+ 	if( !($result = ldap_modify($ldap->connection, "k=kolab,".$_SESSION['base_dn'], $attrs)) ) {
+ 	  $errors[] = sprintf(_("LDAP Error: failed to modify kolab configuration object: %s"),
+ 						  ldap_error($ldap->connection));
+ 	}
++	// Add domain object and quotas
++	if(empty($errors))
++		if($ldap->addToDomainGroups("", $attrs['postfix-mydestination']))
++			if(!($result = ldap_modify($ldap->connection, "cn=" . end($postfixmydestination)
++					. ",cn=domains,cn=internal," . $_SESSION['base_dn'], $domainquotas)))
++				$errors[] = sprintf(_("LDAP Error: failed to add domain quotas: %s"),
++						ldap_error($ldap->connection));
+ 	if(empty($errors))
+ 		if($result = addDomainToCustomer($_REQUEST['adestination'], $_REQUEST['owner']))
+ 			$errors[] = $result;
+@@ -338,6 +359,9 @@ if($_REQUEST['deletecustomer']) {
+ if($_REQUEST['addcustomer']) {
+ 	$customer_description = trim( $_REQUEST['description'] );
+ 	$customer_cn = trim($_REQUEST['customer_cn']);
++	$customer_quota = trim($_REQUEST['customerquota']);
++	if(!$customer_quota || !is_numeric($customer_quota))
++		$customer_quota = 0;
+ 	$attrs = array();
+ 	$attrs['objectClass'] = array('top', 'kolabNamedObject');
+ 	$attrs['cn'] = $customer_cn;
+@@ -366,6 +390,7 @@ if($_REQUEST['addcustomer']) {
+ 				$attrs['description'] = $customer_description;
+ 				$attrs['member'] = "";
+ 				$attrs['objectClass'] = array('top', 'kolabGroupOfNames');
++				$attrs['customerQuota'] = $customer_quota;
+ 				if(!($result = ldap_add($ldap->connection, "cn=" . $customer_cn
+ 						. ",cn=customers,cn=internal," . $_SESSION['base_dn'],
+ 						$attrs)))
+@@ -385,10 +410,28 @@ if($_REQUEST['addcustomer']) {
+ if($_REQUEST['changecustomer']) {
+ 	$customer_description = trim($_REQUEST['description']);
+ 	$customer_cn = trim($_REQUEST['customer_cn']);
++	$customer_quota = trim($_REQUEST['customerquota']);
++	if(!is_numeric($already_used = getCustomerQuotaUsed($customer_cn))) {
++		$errors[] = $already_used;
++		$already_used = 0;
++	}
++	$customer_min_quota = getCustomerUsedDomainQuota($customer_cn);
+ 	if($customer_description == '')
+ 		$customer_description = array();
+ 	$attrs = array();
+ 	$attrs['description'] = $customer_description;
++	if(!preg_match('/^\d*$/', $customer_quota))
++		$errors[] = sprintf(_('Mail quota for customer %s must be an integer'),
++				$customer_description);
++	else if($customer_quota && $customer_quota < $already_used)
++		$errors[] = sprintf(_('Mail quota for customer %s cannot be set below '
++				. '%d MBytes, as this much is already being used'),
++				$customer_description, $already_used);
++	else if($customer_quota && $customer_quota < $customer_min_quota)
++		$errors[] = sprintf(_('Mail quota for customer %s cannot be set below '
++				. '%d MBytes, as this much is already being used'),
++				$customer_description, $customer_min_quota);
++	$attrs['customerQuota'] = $customer_quota ? $customer_quota : 0;
+ 	if(empty($errors))
+ 		if(!($result = ldap_modify($ldap->connection, "cn=" . $customer_cn
+ 				. ",cn=customers,cn=internal," . $_SESSION['base_dn'], $attrs)))
+diff --git a/www/admin/user/user.php.in b/www/admin/user/user.php.in
+index f2bf104..7e31bef 100644
+--- a/www/admin/user/user.php.in
++++ b/www/admin/user/user.php.in
+@@ -13,6 +13,7 @@ require_once('@kolab_php_module_prefix at admin/include/authenticate.php');
+ require_once('@kolab_php_module_prefix at admin/include/form.class.php');
+ require_once('@kolab_php_module_prefix at admin/include/passwd.php');
+ require_once('@kolab_php_module_prefix at admin/include/customer.php');
++require_once('@kolab_php_module_prefix at admin/include/domainquota.php');
+ 
+ /**** Functions ***/
+ function comment( $s ) {
+@@ -562,6 +563,24 @@ switch( $action ) {
+    if( $form->isSubmitted() ) {
+ 	 $mailaddress = trim(strtolower($_POST['user_mail'])) . '@'
+ 			. trim(strtolower($_POST['domain_mail']));
++	 $domainmail = trim($_POST['domain_mail']);
++	 $owner = getCustomerQuota($custom = $ldap->customerOfDomain($domainmail));
++	 $ownerUsed = getCustomerQuotaUsed($custom);
++	 $user = trim($_POST['cyrus-userquota']);
++	 if(!preg_match('/^\d*$/', $user))
++		$errors[] = sprintf(_('User quota must be an integer, not %s'), $user);
++	 else if($user) {
++		// user provided quota, need to check it
++		if($owner && $user + $ownerUsed > $owner)
++			$errors[] = sprintf(_('User Quota exceeds Customer Quota (%d MBytes left out of %d MBytes)'),
++					$owner - $ownerUsed, $owner);
++	 }
++	 else
++		// find our own quota
++		$user = $owner ? $owner - $ownerUsed : '';
++	 if($owner && $ownerUsed == $owner)
++		$errors[] = sprintf(_('No Quota left to create another user for domain %s'), $domainmail);
++	 $_POST['cyrus-userquota'] = $user;
+      if( !$form->validate() ) {
+        $form->setValues();
+        $content = $form->outputForm();
+-- 
+1.7.3.2
+
diff --git a/debian/patches/0003-Mail-aliases-must-match-a-domain-name-space-matching.patch b/debian/patches/0003-Mail-aliases-must-match-a-domain-name-space-matching.patch
new file mode 100644
index 0000000..cdd792f
--- /dev/null
+++ b/debian/patches/0003-Mail-aliases-must-match-a-domain-name-space-matching.patch
@@ -0,0 +1,102 @@
+From 824cf7ed297dbcfce10d1064173e904c9dfb3bd9 Mon Sep 17 00:00:00 2001
+From: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
+Date: Tue, 9 Nov 2010 17:15:16 +0000
+Subject: [PATCH 3/5] Mail aliases must match a domain name space matching the customer
+
+---
+ kolab-webadmin.spec.in                      |    2 +-
+ php/admin/locale/de/LC_MESSAGES/messages.po |    8 +++++
+ www/admin/user/user.php.in                  |   38 ++++++++++++++++++++++++++-
+ 3 files changed, 46 insertions(+), 2 deletions(-)
+
+diff --git a/kolab-webadmin.spec.in b/kolab-webadmin.spec.in
+index 2670ba8..e47d5ff 100644
+--- a/kolab-webadmin.spec.in
++++ b/kolab-webadmin.spec.in
+@@ -31,7 +31,7 @@ Distribution: OpenPKG
+ Group:        Mail
+ License:      GPL
+ Version:      @VERSION@
+-Release:      @spec_build_date at _hosted2
++Release:      @spec_build_date at _hosted3
+ 
+ 
+ #   list of sources
+diff --git a/php/admin/locale/de/LC_MESSAGES/messages.po b/php/admin/locale/de/LC_MESSAGES/messages.po
+index 30cb2bc..5db4942 100644
+--- a/php/admin/locale/de/LC_MESSAGES/messages.po
++++ b/php/admin/locale/de/LC_MESSAGES/messages.po
+@@ -2453,6 +2453,14 @@ msgstr "Benutzer-Kontingent überschreitet Domänenkontingent (%d MBytes von %d
+ msgid "No Quota left to create another user for domain %s"
+ msgstr "Kein Kontingent mehr übrig, um einen weiteren Benutzer für Domäne %s zu erstellen"
+ 
++#: unknown
++msgid "May not have alias in domain %s"
++msgstr "Aliasnamen in Domäne %s ist nicht erlaubt"
++
++#: unknown
++msgid "Not a valid e-mail address: %s"
++msgstr "Keine gültige E-Mail-Adresse: %s"
++
+ #: ../../../www/admin/user/user.php.in:797
+ msgid "LDAP Error: could not add object %s to customer group: %s"
+ msgstr "LDAP Fehler: Konnte Objekt %s nicht zur Kundengruppe hinzufügen: %s"
+diff --git a/www/admin/user/user.php.in b/www/admin/user/user.php.in
+index 7e31bef..1a50fc1 100644
+--- a/www/admin/user/user.php.in
++++ b/www/admin/user/user.php.in
+@@ -153,6 +153,42 @@ function checkdelegate( $form, $key, $value ) {
+   return '';
+ }
+ 
++function check_valid_alias($form, $key, $value) {
++	global $ldap;
++	$result = checkuniquealias($form, $key, $value);
++	if($result)
++		return $result;
++	if(!$value)
++		return '';
++	$action = trim($_REQUEST['action']);
++	if($action == 'firstsave')
++		$user_dn = 'cn=doesnt really matter,' . $_SESSION['customer_dn'];
++	else {
++		$domainmail = trim($_POST['domain_mail']);
++		$customer = $ldap->customerOfDomain($domainmail);
++		$user_dn = 'cn=doesnt really matter,';
++		if($customer)
++			$user_dn .= "cn=$customer,";
++		$user_dn .= $_SESSION['base_dn'];
++	}
++	$domains = getValidAliasDomainsForUser($user_dn);
++	$aliases = preg_split('/[\n\r]+/', $value);
++	$errors = '';
++	foreach($aliases as $alias) {
++		$pos = strpos($alias, '@');
++		if($pos === false) {
++			$errors .= '<br />' . sprintf(_('Not a valid e-mail address: %s'),
++					$alias);
++			continue;
++		}
++		$domain = substr($alias, $pos + 1);
++		if(!in_array($domain, $domains))
++			$errors .= '<br />' . sprintf(_('May not have alias in domain %s'),
++					$domain);
++	}
++	return substr($errors, 6);
++}
++
+ function checksmtprecipient ( $form, $key, $value ) {
+   $lst = array_unique( array_filter( array_map( 'trim', preg_split( '/\n/', $value ) ), 'strlen') );
+   $str = '';
+@@ -488,7 +524,7 @@ $entries = array( 'givenname' => array( 'name' => _('First Name'),
+ 
+ $entries['alias'] = array( 'name' => _('Email Aliases'), 
+ 						   'type' => 'textarea',
+-						   'validation' => 'checkuniquealias',
++						   'validation' => 'check_valid_alias',
+ 						   'comment' => _('One address per line') );
+ 
+ $entries['kolabdelegate'] =array( 'name' => _('Email-Delegates'),
+-- 
+1.7.3.2
+
diff --git a/debian/patches/0004-Add-the-kolabHomeServer-per-customer.patch b/debian/patches/0004-Add-the-kolabHomeServer-per-customer.patch
new file mode 100644
index 0000000..c789878
--- /dev/null
+++ b/debian/patches/0004-Add-the-kolabHomeServer-per-customer.patch
@@ -0,0 +1,256 @@
+From 182ee88caf14bf0ae179478d891e21e038821e97 Mon Sep 17 00:00:00 2001
+From: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
+Date: Tue, 9 Nov 2010 17:16:16 +0000
+Subject: [PATCH 4/5] Add the kolabHomeServer per customer
+
+---
+ kolab-webadmin.spec.in              |    2 +-
+ php/admin/include/auth.class.php.in |    7 +++++++
+ php/admin/include/customer.php      |    2 ++
+ php/admin/include/ldap.class.php.in |   26 ++++++++++++++++++++++++++
+ php/admin/include/mysmarty.php.in   |    4 ++++
+ php/admin/templates/settings.tpl    |    9 +++++++++
+ www/admin/settings/index.php.in     |    9 +++++++--
+ www/admin/user/user.php.in          |    8 +++++++-
+ 8 files changed, 63 insertions(+), 4 deletions(-)
+
+diff --git a/kolab-webadmin.spec.in b/kolab-webadmin.spec.in
+index e47d5ff..cdd4f57 100644
+--- a/kolab-webadmin.spec.in
++++ b/kolab-webadmin.spec.in
+@@ -31,7 +31,7 @@ Distribution: OpenPKG
+ Group:        Mail
+ License:      GPL
+ Version:      @VERSION@
+-Release:      @spec_build_date at _hosted3
++Release:      @spec_build_date at _hosted4
+ 
+ 
+ #   list of sources
+diff --git a/php/admin/include/auth.class.php.in b/php/admin/include/auth.class.php.in
+index 338c1bf..2eba018 100644
+--- a/php/admin/include/auth.class.php.in
++++ b/php/admin/include/auth.class.php.in
+@@ -74,6 +74,7 @@ class KolabAuth {
+ 							'dn'          => $_SESSION['base_dn'],
+ 							'descr'       => _('No customer'),
+ 							'subtree'     => '',
++							'homeserver'  => '',
+ 							'ignore'      => 0
+ 						));
+ 						if(count($customer_dns) > 1)
+@@ -81,6 +82,7 @@ class KolabAuth {
+ 								'dn'          => $_SESSION['base_dn'],
+ 								'descr'       => _('Everything'),
+ 								'subtree'     => '',
++								'homeserver'  => '',
+ 								'ignore'      => 1
+ 							));
+ 						if(count($customer_dns) > 1)
+@@ -120,6 +122,11 @@ class KolabAuth {
+ 						$_SESSION['ignore_customer'] = $auth_group == 'maintainer'
+ 								|| $auth_group == 'admin';
+ 						if(isset($_SESSION['customer_dn']) || $auth_group == "user") {
++							if(empty($_SESSION['customer_subtree']))
++								unset($_SESSION['kolabHomeServer']);
++							else
++								$_SESSION['kolabHomeServer'] = $ldap->homeServerByCustomer(
++										$_SESSION['customer_subtree']);
+ 							// we sorted stuff, better safe than sorry
+ 							switchToCustomer(0);
+ 						}
+diff --git a/php/admin/include/customer.php b/php/admin/include/customer.php
+index d8bd3ae..3ee4229 100644
+--- a/php/admin/include/customer.php
++++ b/php/admin/include/customer.php
+@@ -254,6 +254,7 @@ function adjustSessionForCustomerUpdate($customer, $action, $descr = null, $serv
+ 				'descr' => $descr,
+ 				'subtree' => "cn=$customer,cn=customers,cn=internal,"
+ 						. $_SESSION['base_dn'],
++				'homeserver' => $server,
+ 				'ignore' => 0,
+ 			);
+ 			$_SESSION['customer_dn_options'][] = $info;
+@@ -274,6 +275,7 @@ function adjustSessionForCustomerUpdate($customer, $action, $descr = null, $serv
+ 			if($index == -1)
+ 				break;
+ 			$_SESSION['customer_dn_options'][$index]['descr'] = $descr;
++			$_SESSION['customer_dn_options'][$index]['homeserver'] = $server;
+ 			break;
+ 	}
+ }
+diff --git a/php/admin/include/ldap.class.php.in b/php/admin/include/ldap.class.php.in
+index ad32bce..7cef5b6 100644
+--- a/php/admin/include/ldap.class.php.in
++++ b/php/admin/include/ldap.class.php.in
+@@ -395,6 +395,7 @@ class KolabLDAP {
+ 		  'dn'          => $c_dn,
+ 		  'descr'       => $val['description'][0],
+ 		  'subtree'     => $val['dn'],
++		  'homeserver'  => $val['kolabhomeserver'][0],
+ 		  'ignore'      => 0
+ 		);
+ 	  else
+@@ -403,6 +404,31 @@ class KolabLDAP {
+ 	return $customers;
+   }
+ 
++  function homeServerByCustomer($cust_dn) {
++    if(!$this->is_bound)
++      return '';
++    $result = ldap_read($this->connection, $cust_dn, '(objectClass=kolabGroupOfNames)',
++        array('kolabHomeServer'));
++    if(!ldap_count_entries($this->connection, $result)) {
++      ldap_free_result($result);
++      return '';
++    }
++    $objs = ldap_get_entries($this->connection, $result);
++    ldap_free_result($result);
++    return $objs[0]['kolabhomeserver'][0];
++  }
++
++  // get name of customer's Kolab home server
++  function kolabHomeServerForCustomer($dn = '') {
++    if(!$this->is_bound)
++      return false;
++    $result = $this->search("cn=customers,cn=internal," . $_SESSION['base_dn'],
++        '(&(objectclass=kolabGroupOfNames)(member=' . $dn . '))',
++        array('kolabHomeServer'));
++    $entries = $this->getEntries();
++    return $entries[0]['kolabhomeserver'][0];
++  }
++
+   function domainsForMaintainerDn( $dn ) {
+     if( !$this->is_bound ) {
+       return false;
+diff --git a/php/admin/include/mysmarty.php.in b/php/admin/include/mysmarty.php.in
+index ac2c4de..e9972b0 100644
+--- a/php/admin/include/mysmarty.php.in
++++ b/php/admin/include/mysmarty.php.in
+@@ -30,6 +30,10 @@ function switchToCustomer($idx) {
+ 		$_SESSION['customer_descr'] = $cus['descr'];
+ 		$_SESSION['customer_subtree'] = $cus['subtree'];
+ 		$_SESSION['ignore_customer'] = $cus['ignore'];
++		if(empty($cus['homeserver']))
++			unset($_SESSION['kolabHomeServer']);
++		else
++			$_SESSION['kolabHomeServer'] = $cus['homeserver'];
+ 	}
+ }
+ 
+diff --git a/php/admin/templates/settings.tpl b/php/admin/templates/settings.tpl
+index 8bd2820..7c01521 100755
+--- a/php/admin/templates/settings.tpl
++++ b/php/admin/templates/settings.tpl
+@@ -258,12 +258,14 @@
+ 	<tr class="contentrow">
+ 		<th>{tr msg="Customer ID"}</th>
+ 		<th>{tr msg="Quota (MBytes)"}</th>
++ 		<th>{tr msg="Home Server"}</th>
+ 		<th>{tr msg="Customer name"}</th>
+ 		<th>{tr msg="Action"}</th>
+ 	</tr>
+ {section name=id loop=$customers}
+ 	<tr class="contentrow{cycle values="even,odd"}">
+ 		<td class="contentcell">{$customers[id].cn|escape:"html"}</td>
++		<td class="contentcell">{$customers[id].kolabhomeserver|escape:"html"}</td>
+ 		<form method="post" action="">
+ 		<td class="contentcell">
+ 			<input type="text" name="customerquota" size="4" value="{if $customers[id].customerquota!='0'}{$customers[id].customerquota}{/if}" />
+@@ -293,6 +295,13 @@
+ 				<input type="text" name="customerquota" size="4" />
+ 			</td>
+ 			<td class="contentcell">
++				<select name="kolabhomeserver">
++					{section name=id loop=$kolabhost}
++						<option value="{$kolabhost[id]}">{$kolabhost[id]}</option>
++					{/section}
++				</select>
++			</td>
++			<td class="contentcell">
+ 				<input type="text" size="25" name="description" />
+ 			</td>
+ 			<td class="actioncell">
+diff --git a/www/admin/settings/index.php.in b/www/admin/settings/index.php.in
+index 31227ad..fdbf925 100644
+--- a/www/admin/settings/index.php.in
++++ b/www/admin/settings/index.php.in
+@@ -43,6 +43,7 @@ function getCustomers() {
+ 			array(
+ 				'cn',
+ 				'customerQuota',
++ 				'kolabHomeServer',
+ 				'description'
+ 			)))) {
+ 		ldap_sort($ldap->connection,$result,'cn');
+@@ -53,6 +54,7 @@ function getCustomers() {
+ 					"description" => $v["description"][0],
+ 					'customerquota' => $v['customerquota']['count'] ?
+ 							$v['customerquota'][0] : '',
++					"kolabhomeserver"  => $v["kolabhomeserver"][0],
+ 				);
+ 		return $customer;
+ 	}
+@@ -362,6 +364,7 @@ if($_REQUEST['addcustomer']) {
+ 	$customer_quota = trim($_REQUEST['customerquota']);
+ 	if(!$customer_quota || !is_numeric($customer_quota))
+ 		$customer_quota = 0;
++	$customer_kolabhomeserver = trim($_REQUEST['kolabhomeserver']);
+ 	$attrs = array();
+ 	$attrs['objectClass'] = array('top', 'kolabNamedObject');
+ 	$attrs['cn'] = $customer_cn;
+@@ -388,6 +391,7 @@ if($_REQUEST['addcustomer']) {
+ 				// Create internal customer group object
+ 				$attrs['cn'] = $customer_cn;
+ 				$attrs['description'] = $customer_description;
++				$attrs['kolabHomeServer'] = $customer_kolabhomeserver;
+ 				$attrs['member'] = "";
+ 				$attrs['objectClass'] = array('top', 'kolabGroupOfNames');
+ 				$attrs['customerQuota'] = $customer_quota;
+@@ -432,14 +436,15 @@ if($_REQUEST['changecustomer']) {
+ 				. '%d MBytes, as this much is already being used'),
+ 				$customer_description, $customer_min_quota);
+ 	$attrs['customerQuota'] = $customer_quota ? $customer_quota : 0;
++	$customer_kolabhomeserver = trim($_REQUEST['kolabhomeserver']);
++	$attrs['kolabHomeServer'] = $customer_kolabhomeserver;
+ 	if(empty($errors))
+ 		if(!($result = ldap_modify($ldap->connection, "cn=" . $customer_cn
+ 				. ",cn=customers,cn=internal," . $_SESSION['base_dn'], $attrs)))
+ 			$errors[] = sprintf(_("LDAP Error: failed to modify customer object: %s"),
+ 					ldap_error($ldap->connection));
+ 	if(empty($errors))
+-		adjustSessionForCustomerUpdate($customer_cn, 'modified', $customer_description);
+-}
++ 		adjustSessionForCustomerUpdate($customer_cn, 'modified', $customer_description, $customer_kolabhomeserver)		}
+ 
+ // Delete kolabhost
+ if( $_REQUEST['deletekolabhost'] ) {
+diff --git a/www/admin/user/user.php.in b/www/admin/user/user.php.in
+index 1a50fc1..11e90a7 100644
+--- a/www/admin/user/user.php.in
++++ b/www/admin/user/user.php.in
+@@ -509,7 +509,7 @@ $entries = array( 'givenname' => array( 'name' => _('First Name'),
+ 		  'kolabhomeserver' => array( 'name' => _('Mailbox Home Server'),
+ 									  'validation' => 'notempty',
+ 									  'comment' => $comment_kolabhomeserver,
+-									  'value' => $_SESSION['fqdnhostname'] ),
++									  'value' => $_SESSION['kolabHomeServer'] ),
+ 		  'accttype' => array( 'name' => _('Account Type'),
+ 							   'type' => 'select',
+ 							   'options' => array( _('User Account'), _('Internal User Account'), _('Group Account'), _('Resource Account') ),
+@@ -640,6 +640,12 @@ switch( $action ) {
+        if( $action == 'firstsave' ) {
+ 		 if ($ldap_object['uid'] == "") $ldap_object['uid'] = $ldap_object['mail'];
+ 		 $ldap_object['kolabHomeServer'] = trim($_POST['kolabhomeserver']);
++		 if($ldap_object['kolabHomeServer'] == "") {
++			if(isset($_SESSION["kolabHomeServer"]))
++				$ldap_object['kolabHomeServer'] = $_SESSION["kolabHomeServer"];
++			else
++				$ldap_object['kolabHomeServer'] = $_SESSION["fqdnhostname"];
++		 }
+ 	   } else {
+ 		 unset($ldap_object['kolabHomeServer']);
+ 	   }
+-- 
+1.7.3.2
+
diff --git a/debian/patches/0005-Fix-merge-issues.patch b/debian/patches/0005-Fix-merge-issues.patch
new file mode 100644
index 0000000..7ee7372
--- /dev/null
+++ b/debian/patches/0005-Fix-merge-issues.patch
@@ -0,0 +1,44 @@
+From ffb2ad958486341570707827a3b756e9d0a9a118 Mon Sep 17 00:00:00 2001
+From: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
+Date: Tue, 9 Nov 2010 17:17:11 +0000
+Subject: [PATCH 5/5] Fix merge issues
+
+---
+ php/admin/templates/settings.tpl |    2 +-
+ www/admin/settings/index.php.in  |    3 ++-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/php/admin/templates/settings.tpl b/php/admin/templates/settings.tpl
+index 7c01521..2130b24 100755
+--- a/php/admin/templates/settings.tpl
++++ b/php/admin/templates/settings.tpl
+@@ -265,11 +265,11 @@
+ {section name=id loop=$customers}
+ 	<tr class="contentrow{cycle values="even,odd"}">
+ 		<td class="contentcell">{$customers[id].cn|escape:"html"}</td>
+-		<td class="contentcell">{$customers[id].kolabhomeserver|escape:"html"}</td>
+ 		<form method="post" action="">
+ 		<td class="contentcell">
+ 			<input type="text" name="customerquota" size="4" value="{if $customers[id].customerquota!='0'}{$customers[id].customerquota}{/if}" />
+ 		</td>
++		<td class="contentcell">{$customers[id].kolabhomeserver|escape:"html"}</td>
+ 		<td class="contentcell" nowrap>
+ 			<input type="text" name="description" size="25" value="{$customers[id].description}" />
+ 			<input type="hidden" name="customer_cn" value="{$customers[id].cn}" />
+diff --git a/www/admin/settings/index.php.in b/www/admin/settings/index.php.in
+index fdbf925..27cedcd 100644
+--- a/www/admin/settings/index.php.in
++++ b/www/admin/settings/index.php.in
+@@ -444,7 +444,8 @@ if($_REQUEST['changecustomer']) {
+ 			$errors[] = sprintf(_("LDAP Error: failed to modify customer object: %s"),
+ 					ldap_error($ldap->connection));
+ 	if(empty($errors))
+- 		adjustSessionForCustomerUpdate($customer_cn, 'modified', $customer_description, $customer_kolabhomeserver)		}
++        adjustSessionForCustomerUpdate($customer_cn, 'modified', $customer_description, $customer_kolabhomeserver);
++}
+ 
+ // Delete kolabhost
+ if( $_REQUEST['deletekolabhost'] ) {
+-- 
+1.7.3.2
+
diff --git a/debian/patches/series b/debian/patches/series
index fb5da58..4e2a610 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -2,3 +2,8 @@
 60-no_extra_doc.diff
 70-sieve-port-i18n.diff
 80-issue4025.diff
+0001-Add-customers-to-the-hosted-version-of-the-kolab-web.patch
+0002-Add-quota-on-a-per-customer-basis.patch
+0003-Mail-aliases-must-match-a-domain-name-space-matching.patch
+0004-Add-the-kolabHomeServer-per-customer.patch
+0005-Fix-merge-issues.patch




More information about the packaging-commits mailing list