Skip to content

Instantly share code, notes, and snippets.

@tpokorra
Last active December 15, 2015 10:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tpokorra/5244642 to your computer and use it in GitHub Desktop.
Save tpokorra/5244642 to your computer and use it in GitHub Desktop.
preparing Kolab for multi domain operation; for more details see http://www.tbits.net/tbits-opensource/kolab3multipledomains.html
<?php
$hosted_domain = isset($argv[1])?$argv[1]:"";
$ldappassword = isset($argv[2])?$argv[2]:"";
$hosted_domain_root_dn="dc=".implode(",dc=",explode(".", $hosted_domain));
# Do we have all infos to continue?
if($hosted_domain=="" || $hosted_domain_root_dn=="" || $ldappassword == "") {
die("Usage: ".$argv[0]." <hosted domain> <ldappasswd>\n".
"e.g. ".$argv[0]." kolab.example.org secret\n");
}
# attach code from template to /etc/kolab/kolab.conf
$conf = file_get_contents("domain.kolab.conf.tpl");
$conf = str_replace('{$hosted_domain}', $hosted_domain, $conf);
$conf = str_replace('{$hosted_domain_root_dn}', $hosted_domain_root_dn, $conf);
file_put_contents("/etc/kolab/kolab.conf", $conf, FILE_APPEND | LOCK_EX);
system("service httpd reload");
# add a admin user for this domain
$ds=ldap_connect("localhost") or die("Couldn't connect to LDAP server!");
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_bind($ds, "cn=Directory Manager", "$ldappassword");
$info = array();
$info["ou"] = "ou=people,$hosted_domain_root_dn";
$info["objectclass"] = array();
$info["objectclass"][] = "top";
$info["objectclass"][] = "kolabinetorgperson";
$info["objectclass"][] = "inetorgperson";
$info["objectclass"][] = "mailrecipient";
$info["objectclass"][] = "person";
$info["uid"] = "admin";
$info["cn"] = "Admin";
$info["sn"] = "Admin";
$info["givenname"] = "Admin";
$info["displayName"] = "Admin";
$info["mail"] = "admin@$hosted_domain";
$info["nsroledn"] = "cn=kolab-admin,$hosted_domain_root_dn";
ldap_add($ds, "uid=admin,ou=People,$hosted_domain_root_dn", $info);
# add the domain admin to cn=Directory Administrators, of the domain
$hosteddomain_da_dn = "cn=Directory Administrators,".$hosted_domain_root_dn;
$info = array();
$info["uniquemember"] = array();
$info["uniquemember"][] = "cn=Directory Manager";
$info["uniquemember"][] = "uid=admin,ou=People,$hosted_domain_root_dn";
ldap_modify($ds, $hosteddomain_da_dn, $info);
# allow the admin to see the domain in cn=kolab,cn=config
$associateddomain_dn="associateddomain=$hosted_domain,cn=kolab,cn=config";
$info = array();
$info["aci"] = array();
$info["aci"][] = "(targetattr =\"*\")(version 3.0;acl \"uid=admin,ou=People,$hosted_domain_root_dn\";allow (read,search) (userdn=\"ldap:///uid=admin,ou=People,$hosted_domain_root_dn\");)";
ldap_modify($ds, $associateddomain_dn, $info);
ldap_close($ds);
?>
[{$hosted_domain}]
base_dn = {$hosted_domain_root_dn}
primary_mail = %(givenname)s.%(surname)s@%(domain)s
autocreate_folders = {
'Archive': {
'quota': 0
},
'Calendar': {
'annotations': {
'/private/vendor/kolab/folder-type': "event.default",
'/shared/vendor/kolab/folder-type': "event",
},
},
'Configuration': {
'annotations': {
'/private/vendor/kolab/folder-type': "configuration.default",
'/shared/vendor/kolab/folder-type': "configuration.default",
},
},
'Drafts': {
'annotations': {
'/private/vendor/kolab/folder-type': "mail.drafts",
},
},
'Contacts': {
'annotations': {
'/private/vendor/kolab/folder-type': "contact.default",
'/shared/vendor/kolab/folder-type': "contact",
},
},
'Journal': {
'annotations': {
'/private/vendor/kolab/folder-type': "journal.default",
'/shared/vendor/kolab/folder-type': "journal",
},
},
'Notes': {
'annotations': {
'/private/vendor/kolab/folder-type': 'note.default',
'/shared/vendor/kolab/folder-type': 'note',
},
},
'Sent': {
'annotations': {
'/private/vendor/kolab/folder-type': "mail.sentitems",
},
},
'Spam': {
'annotations': {
'/private/vendor/kolab/folder-type': "mail.junkemail",
},
},
'Tasks': {
'annotations': {
'/private/vendor/kolab/folder-type': "task.default",
'/shared/vendor/kolab/folder-type': "task",
},
},
'Trash': {
'annotations': {
'/private/vendor/kolab/folder-type': "mail.wastebasket",
},
},
}
#!/bin/bash
if [ -z "$1" ]
then
echo "call $0 <ldap password for cn=Directory Manager>"
exit 1
fi
DirectoryManagerPwd=$1
#Removing Canonification from Cyrus IMAP
# TODO: could preserve canonification: http://www.intevation.de/pipermail/kolab-users/2012-August/013747.html
sed -r -i -e 's/^auth_mech/#auth_mech/g' /etc/imapd.conf
sed -r -i -e 's/^pts_module/#pts_module/g' /etc/imapd.conf
sed -r -i -e 's/^ldap_/#ldap_/g' /etc/imapd.conf
service cyrus-imapd restart
#Update Postfix LDAP Lookup Tables
# support subdomains too, search_base = dc=%3,dc=%2,dc=%1
# see http://www.intevation.de/pipermail/kolab-users/2013-January/014270.html
rm -f /etc/postfix/ldap/*_3.cf
for f in `find /etc/postfix/ldap/ -type f -name "*.cf" ! -name "mydestination.cf"`;
do
f3=${f/.cf/_3.cf}
cp $f $f3
sed -r -i -e 's/^search_base = .*$/search_base = dc=%2,dc=%1/g' $f
sed -r -i -e 's/^search_base = .*$/search_base = dc=%3,dc=%2,dc=%1/g' $f3
done
sed -r -i -e 's#^transport_maps = .*$#transport_maps = ldap:/etc/postfix/ldap/transport_maps.cf, ldap:/etc/postfix/ldap/transport_maps_3.cf#g' /etc/postfix/main.cf
sed -r -i -e 's#^virtual_alias_maps = .*$#virtual_alias_maps = $alias_maps, ldap:/etc/postfix/ldap/virtual_alias_maps.cf, ldap:/etc/postfix/ldap/mailenabled_distgroups.cf, ldap:/etc/postfix/ldap/mailenabled_dynamic_distgroups.cf, ldap:/etc/postfix/ldap/virtual_alias_maps_3.cf, ldap:/etc/postfix/ldap/mailenabled_distgroups_3.cf, ldap:/etc/postfix/ldap/mailenabled_dynamic_distgroups_3.cf#g' /etc/postfix/main.cf
sed -r -i -e 's#^local_recipient_maps = .*$#local_recipient_maps = ldap:/etc/postfix/ldap/local_recipient_maps.cf, ldap:/etc/postfix/ldap/local_recipient_maps_3.cf#g' /etc/postfix/main.cf
service postfix restart
# withdraw permissions for all users from the default domain, which is used to manage the domain admins
management_domain=`cat /etc/kolab/kolab.conf | grep primary_domain`
management_domain=${management_domain:17}
cat > ./ldapparam.txt <<END
dn: associateddomain=$management_domain,cn=kolab,cn=config
changetype: modify
delete: aci
END
ldapmodify -x -h localhost -D "cn=Directory Manager" -w $DirectoryManagerPwd -f ./ldapparam.txt
#Configuring Roundcube
patch /usr/share/roundcubemail/plugins/kolab_auth/kolab_auth.php kolab_auth.php.patch
#sed -r -i -e "s#'ou=People,.*'#'ou=People,'.\$domain_base_dn#g" /etc/roundcubemail/main.inc.php
#sed -r -i -e "s#'ou=Groups,.*'#'dc=Groups,'.\$domain_base_dn#g" /etc/roundcubemail/main.inc.php
#sed -r -i -e "s#'ou=People,.*'#'ou=People,'.\$domain_base_dn#g" /etc/roundcubemail/kolab_auth.inc.php
#sed -r -i -e "s#'ou=Groups,.*'#'dc=Groups,'.\$domain_base_dn#g" /etc/roundcubemail/kolab_auth.inc.php
#sed -r -i -e 's#<\?php.*$#<?php if (file_exists(RCUBE_CONFIG_DIR . "/" . $_SERVER["HTTP_HOST"] . "/" . basename(__FILE__))) include_once(RCUBE_CONFIG_DIR . "/" . $_SERVER["HTTP_HOST"] . "/" . basename(__FILE__));#g' /etc/roundcubemail/kolab_auth.inc.php
#Configuring syncroton for Active Sync
patch /usr/share/kolab-syncroton/lib/plugins/kolab_auth/kolab_auth.php syncroton_kolab_auth.php.patch
#Patch WebAdmin for supporting multiple domains per domain admin
scriptDir=`pwd`
cd /usr/share/kolab-webadmin
patch -p0 -i $scriptDir/patchMultiDomainAdmins.patch
cd $scriptDir
--- kolab_auth.php.orig 2013-03-26 15:36:56.388999054 +0100
+++ kolab_auth.php 2013-03-26 16:03:38.886999756 +0100
@@ -32,6 +32,7 @@
{
static $ldap;
private $data = array();
+ static $base_dn="";
public function init()
{
@@ -274,6 +275,9 @@
$pass = $args['pass'];
$loginas = trim(rcube_utils::get_input_value('_loginas', rcube_utils::INPUT_POST));
+ $domain=substr($user,strpos($user, '@') + 1);
+ self::$base_dn="dc=".implode(",dc=",explode(".", $domain));
+
if (empty($user) || empty($pass)) {
$args['abort'] = true;
return $args;
@@ -458,6 +462,10 @@
*/
public static function ldap()
{
+ if (self::$base_dn == "") {
+ return null;
+ }
+
if (self::$ldap) {
return self::$ldap;
}
@@ -485,6 +493,9 @@
return null;
}
+ $addressbook['base_dn'] = "ou=People,".self::$base_dn;
+ $addressbook['groups']['base_dn'] = "ou=Groups,".self::$base_dn;
+
self::$ldap = new kolab_auth_ldap_backend(
$addressbook,
$rcmail->config->get('ldap_debug'),
diff -uNr orig/kolab-webadmin/lib/api/kolab_api_service_domain_types.php /usr/share/kolab-webadmin/lib/api/kolab_api_service_domain_types.php
--- orig/kolab-webadmin/lib/api/kolab_api_service_domain_types.php 2013-04-16 15:50:26.894544164 +0200
+++ /usr/share/kolab-webadmin/lib/api/kolab_api_service_domain_types.php 2013-05-06 12:48:11.922998781 +0200
@@ -64,6 +64,10 @@
'associateddomain' => array(
'type' => 'list',
),
+ 'domainadmin' => array(
+ 'type' => 'list',
+ 'optional' => 'true',
+ ),
'inetdomainbasedn' => array(
'optional' => 'true',
),
diff -uNr orig/kolab-webadmin/lib/Auth/LDAP.php /usr/share/kolab-webadmin/lib/Auth/LDAP.php
--- orig/kolab-webadmin/lib/Auth/LDAP.php 2013-04-16 15:50:26.893544183 +0200
+++ /usr/share/kolab-webadmin/lib/Auth/LDAP.php 2013-05-07 08:26:33.442981366 +0200
@@ -43,7 +43,7 @@
// Causes nesting levels to be too deep...?
//$this->config_set('config_get_hook', array($this, "_config_get"));
- $this->config_set("debug", true);
+ $this->config_set("debug", false);
$this->config_set("log_hook", array($this, "_log"));
//$this->config_set("vlv", false);
@@ -132,6 +132,18 @@
$_SESSION['user']->user_bind_dn = $result;
$_SESSION['user']->user_bind_pw = $password;
+ # if the user does not have access to the default domain, set another domain
+ $domains = $this->list_domains();
+ $domain = "";
+ foreach ($domains['list'] as $key => $value) {
+ $domain = $value['associateddomain'];
+
+ if ($domain == $this->domain) {
+ break;
+ }
+ }
+ $_SESSION['user']->set_domain($domain);
+
return $result;
}
@@ -143,7 +155,7 @@
if ($domain_info === false) {
$this->_domain_add_new($parent_domain, $prepopulate);
}
-
+#TODO store domain admin?
return $this->_domain_add_alias($domain, $parent_domain);
}
else {
@@ -151,6 +163,93 @@
}
}
+ private function ChangeDomainReadCapability($user, $domain, $action='add')
+ {
+ if (($tmpconn = ldap_connect($this->_ldap_server)) === false) {
+ return false;
+ }
+
+ if (ldap_bind($tmpconn, $_SESSION['user']->user_bind_dn, $_SESSION['user']->user_bind_pw) === false) {
+ ldap_close($tmpconn);
+ return false;
+ }
+
+ $associateddomain_dn="associateddomain=$domain,cn=kolab,cn=config";
+ $info = array();
+ $info["aci"] = array();
+ if (!(($sr = ldap_read($tmpconn, $associateddomain_dn, "(aci=*)", array('aci'))) === false)) {
+ $entry = ldap_get_entries($tmpconn, $sr);
+ if ($entry['count'] > 0) {
+ for ($count = 0; $count < $entry[0]['aci']['count']; $count++) {
+ if (strpos($entry[0]['aci'][$count], $user) === false) {
+ $info['aci'][] = $entry[0]['aci'][$count];
+ }
+ }
+ }
+ }
+
+ if ($action == 'add') {
+ $info["aci"][] = "(targetattr =\"*\")(version 3.0;acl \"$user\";allow (read,search) (userdn=\"ldap:///$user\");)";
+ }
+
+ if (ldap_modify($tmpconn, $associateddomain_dn, $info) === false) {
+ ldap_close($tmpconn);
+ return false;
+ }
+
+ ldap_close($tmpconn);
+ return true;
+ }
+
+ private function domain_admin_save($domain, $domain_dn, $attributes) {
+ $currentdomain_dn = $this->_standard_root_dn($domain[$domain_dn]["associateddomain"]);
+ $currentdomain_da_dn = "cn=Directory Administrators,".$currentdomain_dn;
+
+ $domain_admins_result = $this->_search($currentdomain_dn, "cn=Directory Administrators*", array("uniqueMember"));
+ if ($domain_admins_result != null && count($domain_admins_result) > 0) {
+ $domain_admins = $domain_admins_result->entries(true);
+ }
+
+ if (empty($domain_admins[$currentdomain_da_dn]["uniquemember"])) {
+ $domain_admins[$currentdomain_da_dn]["uniquemember"] = Array();
+ }
+
+ if (!is_array($domain_admins[$currentdomain_da_dn]["uniquemember"])) {
+ $domain_admins[$currentdomain_da_dn]["uniquemember"] =
+ (array)($domain_admins[$currentdomain_da_dn]["uniquemember"]);
+ }
+
+ $info = array();
+ $info["uniquemember"] = array();
+ for ($count = 0; $count < count($attributes["domainadmin"]); $count++) {
+ $info["uniquemember"][] = $attributes["domainadmin"][$count];
+
+ if (!in_array($attributes["domainadmin"][$count], $domain_admins[$currentdomain_da_dn]["uniquemember"])) {
+ # add read permission to associateddomain in cn=kolab,cn=config
+ $this->ChangeDomainReadCapability($attributes["domainadmin"][$count], $domain[$domain_dn]["associateddomain"], 'add');
+ }
+ }
+
+ # check for removed admins: remove also read permission from associateddomain in cn=kolab,cn=config
+ foreach ($domain_admins[$currentdomain_da_dn]["uniquemember"] as $oldadmin) {
+ if (!in_array($oldadmin, $attributes["domainadmin"])) {
+ if ($oldadmin == "cn=Directory Manager") {
+ # make sure that Directory Manager is still in the list
+ $info["uniquemember"][] = "cn=Directory Manager";
+ } else {
+ # drop read permission to associateddomain in cn=kolab,cn=config
+ $this->ChangeDomainReadCapability($oldadmin, $domain[$domain_dn]["associateddomain"], 'remove');
+ }
+ }
+ }
+
+ $result = $this->modify_entry($currentdomain_da_dn, $domain_admins[$currentdomain_da_dn], $info);
+
+ if (!$result) {
+ return false;
+ }
+ }
+
public function domain_edit($domain, $attributes, $typeid = null)
{
$domain = $this->domain_info($domain, array_keys($attributes));
@@ -161,6 +260,12 @@
$domain_dn = key($domain);
+ # using isset, because if the array is empty, then we want to drop the domain admins.
+ if (isset($attributes["domainadmin"])) {
+ $this->domain_admin_save($domain, $domain_dn, $attributes);
+ unset($attributes["domainadmin"]);
+ }
+
// We should start throwing stuff over the fence here.
return $this->modify_entry($domain_dn, $domain[$domain_dn], $attributes);
}
@@ -195,6 +300,7 @@
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() uses _search()");
$result = $this->_search($domain_base_dn, $domain_filter, $attributes);
$result = $result->entries(true);
+ $domain_dn = key($result);
} else {
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() uses _read()");
$result = $this->_read($domain_dn, $attributes);
@@ -204,6 +310,25 @@
return false;
}
+ $currentdomain_dn = $this->_standard_root_dn($result[$domain_dn]["associateddomain"]);
+ $currentdomain_da_dn = "cn=Directory Administrators,".$currentdomain_dn;
+
+ $domain_admins_result = $this->_search($currentdomain_dn, "cn=Directory Administrators*", array("uniqueMember"));
+ if ($domain_admins_result != null && count($domain_admins_result) > 0) {
+ $domain_admins = $domain_admins_result->entries(true);
+ }
+
+ // read domain admins from LDAP, uniqueMembers of Directory Administrators of domain
+ $result[$domain_dn]["domainadmin"] = array();
+ if (is_array($domain_admins[$currentdomain_da_dn]["uniquemember"])) {
+ foreach ($domain_admins[$currentdomain_da_dn]["uniquemember"] as $domainadmin) {
+ $result[$domain_dn]["domainadmin"][] = $domainadmin;
+ }
+ }
+ else {
+ $result[$domain_dn]["domainadmin"][] = $domain_admins[$currentdomain_da_dn]["uniquemember"];
+ }
+
$this->_log(LOG_DEBUG, "Auth::LDAP::domain_info() result: " . var_export($result, true));
return $result;
diff -uNr orig/kolab-webadmin/lib/client/kolab_client_task_domain.php /usr/share/kolab-webadmin/lib/client/kolab_client_task_domain.php
--- orig/kolab-webadmin/lib/client/kolab_client_task_domain.php 2013-04-16 15:50:26.895544147 +0200
+++ /usr/share/kolab-webadmin/lib/client/kolab_client_task_domain.php 2013-05-06 12:48:11.925998781 +0200
@@ -224,6 +224,7 @@
$sections = array(
'system' => 'domain.system',
'other' => 'domain.other',
+ 'admins' => 'domain.admins',
);
// field-to-section map and fields order
@@ -231,6 +232,7 @@
'type_id' => 'system',
'type_id_name' => 'system',
'associateddomain' => 'system',
+ 'domainadmin' => 'admins',
);
//console("domain_form() \$data", $data);
diff -uNr orig/kolab-webadmin/lib/kolab_api_service.php /usr/share/kolab-webadmin/lib/kolab_api_service.php
--- orig/kolab-webadmin/lib/kolab_api_service.php 2013-04-16 15:50:26.893544183 +0200
+++ /usr/share/kolab-webadmin/lib/kolab_api_service.php 2013-05-06 12:48:11.931998781 +0200
@@ -86,6 +86,9 @@
'associateddomain' => array(
'type' => 'list'
),
+ 'domainadmin' => array(
+ 'type' => 'list'
+ ),
),
'fields' => array(
'objectclass' => array(
--- kolab_auth.php.orig 2013-04-15 12:07:43.000000000 +0200
+++ kolab_auth.php 2013-04-15 11:10:27.605569812 +0200
@@ -32,6 +32,7 @@
{
private $ldap;
private $data = array();
+ private $base_dn="";
public function init()
{
@@ -258,6 +259,10 @@
{
$this->load_config();
+ $user=$args['user'];
+ $domain=substr($user,strpos($user, '@') + 1);
+ $this->base_dn="dc=".implode(",dc=",explode(".", $domain));
+
if (!$this->init_ldap()) {
$args['abort'] = true;
return $args;
@@ -437,6 +442,10 @@
*/
private function init_ldap()
{
+ if ($this->base_dn == "") {
+ return null;
+ }
+
if ($this->ldap) {
return $this->ldap->ready;
}
@@ -453,6 +462,10 @@
if (empty($addressbook)) {
return false;
}
+
+ // for supporting multiple domains
+ $addressbook['base_dn'] = "ou=People,".$this->base_dn;
+ $addressbook['groups']['base_dn'] = "ou=Groups,".$this->base_dn;
$this->ldap = new kolab_auth_ldap_backend(
$addressbook,
@urkle
Copy link

urkle commented Sep 7, 2013

So, there are a few issues I have seen with this script.. one.. replacing all the postfix with dc=%2,dc=%1 isn't quite correct as two of those .cf files should have ou=Group,dc=%2,dc=%1

@urkle
Copy link

urkle commented Sep 8, 2013

ooh.. you don't need either of the kolab_auth patches... instead just tweak the roundcube configuration files.. to use %dc for the management domain for the base_dn attributes in kolab_auth.inc.php, and main.inc.php..

Note you'll also need to do this for password.inc.php (password_ldap_basedn, and passwd_ldap_search_base) but you'll have to tweak the ldap.php in the password/drivers to call $this->substitute_vars() around them..

well. mostly.. they seem to not universally apply the %dc replace,... fixing..

@urkle
Copy link

urkle commented Sep 9, 2013

OK.. have a fork of this gist with my tweaks. https://gist.github.com/urkle/6489680 my tweaks get password changing in Roundcube working and get the global address book working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment