Skip to content

Instantly share code, notes, and snippets.

@tpokorra
Last active December 27, 2015 10:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tpokorra/7315298 to your computer and use it in GitHub Desktop.
Save tpokorra/7315298 to your computer and use it in GitHub Desktop.
upgrade ldif data from Kolab 2.3 to Kolab 3.x
<?php
# Timotheus Pokorra, for TBits.net, April 2013
#
# import ldif file that has been dumped from Kolab2
# restoring domains, domain admins, and users
require_once "/usr/share/kolab-webadmin/lib/functions.php";
function debug($msg){
if(is_writeable("/tmp/mylog.log")){
$fh = fopen("/tmp/mylog.log",'a+');
fputs($fh,"[UPGRADE] $msg\n");
fclose($fh);
}
}
function firstChar($s, $cmp)
{
return (strlen($s) > 0 && $s[0] == $cmp);
}
function endsWith($haystack, $needle)
{
$length = strlen($needle);
if ($length == 0) {
return true;
}
return (substr($haystack, -$length) === $needle);
}
# parse ldif file for upgrade from kolab2 to kolab3
function parseLDIF($filename)
{
$dn_objects = array();
$lines = file($filename);
$counter = 0;
$current_dn = '';
while ($counter < count($lines)) {
$currentLine = trim($lines[$counter]);
$counter++;
while ($counter < count($lines) && firstChar($lines[$counter], ' ')) {
$currentLine .= trim($lines[$counter]);
$counter++;
}
if (firstChar($currentLine, '#') || strlen($currentLine) == 0) {
// skip comment and empty lines
continue;
}
if (strpos($currentLine, ": ") === false) {
if (strpos($currentLine, ":") === false) {
echo "cannot parse line $counter: $currentLine\n";
} else {
# ignore empty values
}
} else {
$pos = strpos($currentLine, ':: ');
if ($pos !== false) {
$pair = array(substr($currentLine, 0, $pos), base64_decode(substr($currentLine, $pos + 3)));
}
else
{
$pos = strpos($currentLine, ': ');
$pair = array(substr($currentLine, 0, $pos), substr($currentLine, $pos + 2));
}
if ($pair[0] == "dn") {
$current_dn = $pair[1];
$dn_objects[$current_dn] = array();
} else {
if (isset($dn_objects[$current_dn][$pair[0]])) {
if (!is_array($dn_objects[$current_dn][$pair[0]])) {
$dn_objects[$current_dn][$pair[0]] = array($dn_objects[$current_dn][$pair[0]]);
}
$dn_objects[$current_dn][$pair[0]][] = $pair[1];
} else if ($pair[0] == "domains" || $pair[0] == "member") {
# always use an array, to make it easier to access
$dn_objects[$current_dn][$pair[0]] = array($pair[1]);
} else {
$dn_objects[$current_dn][$pair[0]] = $pair[1];
}
}
}
}
return $dn_objects;
}
function importDomain($domain_name)
{
global $auth;
if ($auth->domain_info($domain_name) === false) {
$attribs = array();
$attribs['objectclass'] = array("top", "domainrelatedobject", "inetdomain");
if ($auth->domain_add($domain_name, $attribs) === false) {
die("failed to add domain " .$domain_name);
}
return true;
}
return false;
}
function importUser($domain_name, $person)
{
global $auth;
$person["ou"] = "ou=People,dc=".implode(",dc=", explode(".", $domain_name));
# $person["uid"] is already set
# TODO add kolabhomeserver, etc to schema
unset($person['kolabHomeServer']);
unset($person['lastLogin']);
# add objectClass mailRecipient, so that attribute mailQuota can be set
$person['objectClass'][] = 'mailRecipient';
# mailQuota is in KiloByte, cyrus-userquota was in MegaByte
$person['mailQuota'] = $person['cyrus-userquota'] * 1024;
unset($person['cyrus-userquota']);
# some users have these attributes, which are not known:
# /var/log/kolab-webadmin/errors: ldap_add(): Add: Object class violation in /usr/share/kolab-webadmin/lib/ext/Net/LDAP3.php on line 212
# /var/log/dirsrv/slapd-centos6-kolab3/errors: Entry "uid=pop-....,ou=People,..." -- attribute "c" not allowed
unset($person['search']);
unset($person['result']);
unset($person['c']);
if (empty($person["displayName"])) {
$person["displayName"] = $person["sn"].", ".$person["givenName"];
}
# Roundcube UI will use this language
if (!isset($person['preferredLanguage'])) {
$person['preferredLanguage'] = "de_DE";
}
if (strpos($person["givenName"], ' ') !== false)
{
#TODO mail should not be made up of givenName. Kolab3 skips the space for new users. but Kolab2 just had the first part of the givenname
#die (print_r($person, true));
# $person['mail'] = substr($person["givenName"], 0, strpos($person["givenName"], ' '));
}
#echo "adding person ".print_r($person, true);
# check if user already exists
if ($auth->user_info("uid=".$person["uid"].",".$person["ou"]) === false) {
$auth->user_add($person);
}
}
function importUsers($dn_objects, $domain_name)
{
foreach ($dn_objects as $name => $value) {
if (endsWith($name, ",cn=customers,cn=internal,dc=kolab,dc=tbits,dc=net")) {
# if this customer contains the domain_name
if (in_array($domain_name, $value["domains"])) {
# go through all members, and check their email address
foreach ($value["member"] as $member_dn) {
$person = $dn_objects[$member_dn];
if (endsWith($person['mail'], "@".$domain_name)) {
importUser($domain_name, $person);
}
}
}
}
}
}
# add the domain admin as a user to kolab.tbits.net, so that all domain admins are in one user list
function importDomainAdmin($dn_objects, $default_domain_dn, $person)
{
global $primary_domain;
global $auth;
$person["ou"] = "ou=People,".$default_domain_dn;
if (empty($person["displayName"])) {
$person["displayName"] = $person["cn"];
}
if (empty($person["givenName"])) {
$person["givenName"] = trim(str_replace($person["sn"], "", $person["cn"]));
}
$person['objectClass'][] = 'mailRecipient';
# use the customerQuota of the customer object
$personuid = explode('-', $person["uid"]);
$customerid = $personuid[1];
$customer = $dn_objects["cn=$customerid,cn=customers,cn=internal,dc=kolab,dc=tbits,dc=net"];
$person['mailQuota'] = $customer['customerQuota']*1024;
$person['mail'] = $person["uid"]."@".$primary_domain;
#TODO add role kolab_admin
# check if domain admin already exists
if ($auth->user_info("uid=".$person["uid"].",".$person["ou"]) === false) {
$auth->user_add($person);
}
}
function importDomainAdmins($dn_objects, $domain_name)
{
global $primary_domain;
global $auth;
$default_domain_dn = "dc=".implode(",dc=", explode(".", $primary_domain));
# go through all domain maintainers
foreach ($dn_objects["cn=domain-maintainer,cn=internal,dc=kolab,dc=tbits,dc=net"]["member"] as $domainmaintainer) {
$domainmaintainer_object = $dn_objects[$domainmaintainer];
$domainadmin = "uid=".$domainmaintainer_object['uid'].",ou=People,".$default_domain_dn;
# check the current domain and see if the domain maintainer owns this domain
$domain = $dn_objects["cn=$domain_name,cn=domains,cn=internal,dc=kolab,dc=tbits,dc=net"];
# if this domain admin owns the domain
if ($domain != null && $domain["member"] != null && in_array($domainmaintainer, $domain["member"])) {
# create this user in the default domain
importDomainAdmin($dn_objects, $default_domain_dn, $domainmaintainer_object);
# add permissions for this domain
$domainToSave = $auth->domain_info($domain["cn"]);
$domaindata = $domainToSave[key($domainToSave)];
if (!in_array($domainadmin, $domaindata["domainadmin"])) {
$domaindata["domainadmin"][] = $domainadmin;
$auth->domain_edit($domain["cn"], $domaindata);
}
}
}
}
function importDomains($dn_objects)
{
$count = 0;
$domainsImported = 0;
$domains = null;
if (file_exists("domains.txt"))
{
$domains = file("domains.txt", FILE_IGNORE_NEW_LINES);
}
# go through all domains
foreach ($dn_objects as $name => $value) {
if (strpos($name, ",cn=domains,cn=internal,dc=kolab") !== false) {
$domain_name = $value["cn"];
if ($domains != null && !in_array($domain_name, $domains)) {
continue;
}
echo $domain_name."\n";
if (importDomain($domain_name)) {
$domainsImported++;
}
# add users
importUsers($dn_objects, $domain_name);
# add domain admins
importDomainAdmins($dn_objects, $domain_name);
# TODO [domainQuota] => 5000
# [domainDefaultQuota] => 500
# [maxAccounts] => 50
#if ($count > 4) die("stop at 4 domains");
$count++;
if ($domainsImported == 10) {
# restart ldap server to avoid too many open files
# this does not work, since we lose the connection to the ldap server
return false;
system("service dirsrv restart");
$domainsImported = 0;
}
}
}
return true;
}
function upgrade($filename)
{
$dn_objects = parseLDIF($filename);
return importDomains($dn_objects);
}
$filename = isset($argv[1])?$argv[1]:"";
$ldappassword = isset($argv[2])?$argv[2]:"";
# Do we have all infos to continue?
if($filename == "" || $ldappassword == "") {
die("Usage: ".$argv[0]." <kolab2_ldif_filename> <ldappwd> \n".
"e.g. ".$argv[0]." ldap20130430.txt secret \n");
}
# cancel if kolabd is still running, so that no mailboxes etc are created during the upgrade
$status = system("service kolabd status");
if ($status != "kolabd is stopped") {
echo "please run \"service kolabd stop\" before importing the ldap data!\n";
exit(1);
}
$conf = Conf::get_instance();
$primary_domain = $conf->get('kolab', 'primary_domain');
$_SESSION['user'] = new User();
$valid = $_SESSION['user']->authenticate("cn=Directory Manager", $ldappassword, $primary_domain);
if ($valid === false) {
die ("cannot authenticate user cn=Directory Manager");
}
$auth = Auth::get_instance();
if (!upgrade($filename)) {
echo "\n\nPlease run this script again, we did not finish processing all domains!\n\n";
exit(2);
}
# for changes to kolab.conf to take effect
system("service httpd reload");
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment