Forums

vlad
vlad
Offline
Resolved
0 votes
Hi All,

First of all I would like to mention that this is not a question, I would just like to share some of the fun I've had with ClearOS last couple of days.
I have just installed a new ClearOS and want to migrate existing infrastructure (LDAP database+Mail) towards ClearOS.
As I would want to have the same users, passwords and groups I've had in our legacy infrastructure migrated towards the LDAP of the newly installed ClearOS, I looked around and found no tools for migration of the LDAP database so I figured I should write a short script to do that.
I would like to share the script as I hope it can be useful to people who would have the same needs.

First a couple of words on the task (I have hard-coded a few values so understanding my task might help to rewrite the script according to your needs).
I want to migrate just the users and the groups from the old LDAP. All of them are posix users and groups.

So the legacy user entries would look like:


dn: cn=FirstName Lastname,ou=Users,dc=example,dc=com
cn: FirstName Lastname
uid: username
homeDirectory: /home/username
mail: username@example.com
objectClass: CourierMailAccount
objectClass: account
objectClass: posixAccount
uidNumber: 2000
gidNumber: 63000
structuralObjectClass: account
entryUUID: XXXXXXX-XXXX-XXXX-XXXX-XXXXXXX
creatorsName: cn=Manager,dc=example,dc=com
createTimestamp: YYYYYYYYYYYYYYYYYYYY
userPassword:: ZZZzzZZZZzzZZZZzzzZZZZZzzZZZZzzzZZZZ=
entryCSN: YYYYYYYYYYYYYYYYYYYYY#000000#00#000000
modifiersName: cn=Manager,dc=example,dc=com
modifyTimestamp: YYYYYYYYYYYYYYYYYYYY


While the ClearOS entries should look like:


dn: cn=Firstname Lastname,ou=Users,ou=Accounts,dc=example,dc=com
uid: username
givenName: Firstname
sn: Lastname
objectClass: top
objectClass: posixAccount
objectClass: shadowAccount
objectClass: inetOrgPerson
objectClass: clearAccount
objectClass: clearMailAccount
cn: Firstname Lastname
uidNumber: 2000
loginShell: /sbin/nologin
gidNumber: 63000
homeDirectory: /home/username
clearAccountStatus: enabled
mail: username@example.com


The situation with the entries for the groups was almost the same.
My Legacy group entries look like this:


dn: cn=group1,ou=Groups,dc=example,dc=com
objectClass: posixGroup
gidNumber: 212
cn: group1
memberUid: user1
memberUid: user2
description: Test Group
structuralObjectClass: posixGroup
entryUUID: XXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX
creatorsName: cn=Manager,dc=example,dc=com
createTimestamp: YYYYYYYYYYYYYYYYYY
entryCSN: YYYYYYYYYYYY#000000#00#000000
modifiersName: cn=Manager,dc=example,dc=com
modifyTimestamp: YYYYYYYYYYYYYYYY


While ClearOS groups by default come like this:


dn: cn=group1,ou=Groups,ou=Accounts,dc=example,dc=com
description: Test Group
gidNumber: 60002
cn: group1
objectClass: top
objectClass: posixGroup
objectClass: groupOfNames
objectClass: sambaGroupMapping
sambaSID: -60002
sambaGroupType: 2
displayName: group1
clearMailDistributionList: 0
member: cn=No Members,ou=Users,ou=Accounts,dc=example,dc=com


Basically I had to get rid of some keys and change other keys.
Below you can find a perl script that did the magic for me.
It prints the changed ldif entries to the stdio.

I hope it would help other people like me who are migrating existing LDAP users to newly installed ClearOS.


#!/usr/bin/perl

use strict;
use warnings;


# TODO: Set Filename of exported ldif here.
my $in='ldapusers.ldif';


my @users;
my $entry={};
my $i=0;

## Read the old ldif
open IN , "<$in" or die "No such file: $in\n";
while (<IN>;) {
if ($_ =~ /^\n/) {
push @users, $entry;
$entry={};
} else {
my ($key,$value,$password) = split(/:/,$_);
# Trim the value
if ($key eq "userPassword") {
$value = ":$password";
$entry->{$key}=$value;
# TODO: keys with multiple lines per entry.
} elsif ($key eq "objectClass" or $key eq "memberUid" or $key eq 'member') {
$value =~ s/^\s+|\s+$//g;
if ( exists $entry->{$key} ) {
if ( is_array($entry->{$key}) ) {
push ( $entry->{$key}, $value );
} else {
$entry->{$key} = [$entry->{$key},$value];
}
} else {
$entry->{$key} = $value;
}
} else {
$value =~ s/^\s+|\s+$//g;
$entry->{$key}=$value;
}
}
}

close IN;

## TODO: This needs to be changed according to what the old ldif looks like and according to any specific needs
foreach (@users) {
## Debugging:
#print ">>> Processing: $_->{'dn'}\n";
addToDN($_,"ou=Accounts");
removeKey($_,"structuralObjectClass");
if (ifUser($_)){
my ($firstName, $lastName) = split (/ /,$_->{'cn'});
$lastName=$firstName unless defined $lastName;

removeAttribute($_,"objectClass","CourierMailAccount");
removeAttribute($_,"objectClass","account");
addAttribute ($_,'objectClass','top');
addAttribute ($_,'objectClass','shadowAccount');
addAttribute ($_,'objectClass','inetOrgPerson');
addAttribute ($_,'objectClass','clearAccount');
addAttribute ($_,'objectClass','clearMailAccount');
addAttribute ($_,'loginShell','/sbin/nologin') unless exists $_->{'loginShell'};
addAttribute ($_,'clearAccountStatus','enabled');
addAttribute ($_,'givenName',$firstName);
addAttribute ($_,'sn',$lastName);
} elsif (ifGroup($_)){
addAttribute ($_,'objectClass','top');
addAttribute ($_,'objectClass','groupOfNames');
addAttribute ($_,'objectClass','sambaGroupMapping');
addAttribute ($_,'sambaSID','-'.$_->{'gidNumber'});
addAttribute ($_,'sambaGroupType','2');

# Replace all memberUid:<UID> with member: <dn>
if ( exists $_->{'memberUid'} ) {
if (is_array($_->{'memberUid'})) {
my @memberUids=@{$_->{'memberUid'}};
removeKey($_,'memberUid');
foreach my $m (@memberUids) {
if ( my $e=searchUid($m)) {
addAttribute($_,'member',${$e}->{'dn'});
}
}
} else {
if ( my $n = searchUid($_->{'memberUid'}) ) {
addAttribute($_,'member',${$n}->{'dn'});
removeKey($_,'memberUid');
} else {
addAttribute($_,'member','cn=no members,ou=users,ou=accounts,dc=iis,dc=bg');
}
}
} else {
## TODO : Hardcoded value, To be changed in case of need
addAttribute($_,'member','cn=no members,ou=users,ou=accounts,dc=iis,dc=bg');
}
}
printEntry($_);
}


sub searchUid {
my $uid = shift;
foreach (@users) {
next unless defined $_->{'uid'};
return \$_ if ( ${$_}{'uid'} eq $uid);
}
return 0;
}

sub ifUser {
my $e = shift;
foreach (split(/,/,$e->{"dn"})) {
my ($k,$v) = split(/=/,$_);
return 1 if ($k eq "ou" && $v eq "Users");
}
return 0;
}

sub ifGroup {
my $e = shift;
foreach (split(/,/,$e->{"dn"})) {
my ($k,$v) = split(/=/,$_);
return 1 if ($k eq "ou" && $v eq "Groups");
}
return 0;
}

#TODO: The position of what is added to the dn is hardcoded. Must be changed in case of a need.
sub addToDN {
my $e = shift; # expecte reference to entry;
my $x = shift; # expected : "ou=Accounts"
my @dn =split(/,/,$e->{'dn'});
my $index = $#dn-1;
splice @dn, $index, 0, $x;
$e->{'dn'}=join(',',@dn);
#Debug
#print ">>> DN Changed to : ".$e->{'dn'}."\n";

}

sub removeKey {
my $e = shift;
my $key = shift;
delete $e->{$key};
}

sub addAttribute {
my $e = shift;
my $key = shift;
my $value = shift;

return -1 unless (defined $value and defined $key and defined $e);

unless ( defined $e->{$key} ) {
$e->{$key} = $value ;
} else {
if (is_array($e->{$key})) {
push @{$e->{$key}},$value;
} else {
$e->{$key}=[$e->{$key},$value];
}
}
}

sub removeAttribute {
my $e = shift;
my $key = shift;
my $value = shift;
return -1 unless defined $value and defined $key;
if (is_array($e->{$key})) {
for (my $n=0;$n<=$#{$e->{$key}};$n++){
## DEBUG:
#print '>>> $e->{$key}[$n] eq $value is ';
#print "true\n" if (${$e->{$key}}[$n] eq $value);
#print "false\n" unless (${$e->{$key}}[$n] eq $value);
splice (@{$e->{$key}},$n,1) if (${$e->{$key}}[$n] eq $value);
}
} else {
delete $e->{$key} if (defined $e->{$key} and $e->{$key} eq $value);
}
}

sub printEntry {
my $e = shift;

#dn first
print "dn: $e->{dn}\n";

#cn follows
print "cn: $e->{cn}\n";

#print object Classes
if (is_array($e->{objectClass})) {
foreach (@{$e->{objectClass}}) {
print "objectClass: $_\n";
}
} else {
print "objectClass: $e->{objectClass}\n";
}

#print all the rest
foreach ( sort keys %{$e}){
unless ($_ eq "dn" or $_ eq "cn" or $_ eq "objectClass" or $_ eq "userPassword") {
if (is_array($e->{$_})) {
foreach my $v ( @{$e->{$_}}) {
print "$_: $v\n";
}
} else {
print "$_: $e->{$_}\n"
}
}
print "$_:$e->{$_}\n" if ($_ eq "userPassword");
}
print "\n";

}


sub is_array {
my ($ref) = @_;
# Firstly arrays need to be references, throw
# out non-references early.
return 0 unless ref $ref;

# Now try and eval a bit of code to treat the
# reference as an array. If it complains
# in the 'Not an ARRAY reference' then we're
# sure it's not an array, otherwise it was.
eval {
my $a = @$ref;
};
if ($@=~/^Not an ARRAY reference/) {
return 0;
} elsif ($@) {
die "Unexpected error in eval: $@\n";
} else {
return 1;
}

}

Wednesday, February 07 2018, 02:16 PM
Share this post:
Responses (3)
  • Accepted Answer

    Friday, February 09 2018, 04:57 PM - #Permalink
    Resolved
    0 votes
    Your knowledge of LDAP is vastly greater than mine. It is great to have you on board in the community!
    The reply is currently minimized Show
  • Accepted Answer

    vlad
    vlad
    Offline
    Friday, February 09 2018, 04:38 PM - #Permalink
    Resolved
    0 votes
    Hi, Thank you the reply.
    It is great to know that there is an easy way for migration between ClearOS6 and ClearOS7. One more benefit from migrating towards it.
    The point here is that I am migrating from an outdated gentoo based system. Basically rebuilding all the services I've had there on the newly install ClearOS7. Also I decided not to use the same configurations but to configure everything from scratch and migrate just the data so that I can maintain the same user experience.
    In the above example the I used the output from "slapcat -n2", removed the "default" entries that have replacements and the new installation and the result I supplied as an input for the script.
    The output from the script I used as an input towards "slapadd -n3". I must highlight that a backup of the DB of the new installation is required as the slapadd is very likely to fail a couple of times until managing all the correct schema entries for the new configuration.
    The reply is currently minimized Show
  • Accepted Answer

    Thursday, February 08 2018, 12:09 PM - #Permalink
    Resolved
    0 votes
    Thanks for posting this. If you wanted to bring more than just LDAP across, a config backup from 6.x will restore into 7.x and bring all users across and a lot of app settings and certificates.
    The reply is currently minimized Show
Your Reply