diff options
Diffstat (limited to 'ldap')
-rw-r--r-- | ldap/admin/src/scripts/DSDialogs.pm | 2 | ||||
-rw-r--r-- | ldap/admin/src/scripts/DSMigration.pm.in | 504 | ||||
-rw-r--r-- | ldap/admin/src/scripts/FileConn.pm | 57 | ||||
-rw-r--r-- | ldap/admin/src/scripts/Migration.pm.in | 73 | ||||
-rw-r--r-- | ldap/admin/src/scripts/Util.pm.in | 40 | ||||
-rw-r--r-- | ldap/admin/src/scripts/migrate-ds.res | 11 | ||||
-rw-r--r-- | ldap/admin/src/scripts/setup-ds.pl.in | 11 | ||||
-rw-r--r-- | ldap/admin/src/scripts/setup-ds.res.in | 9 |
8 files changed, 517 insertions, 190 deletions
diff --git a/ldap/admin/src/scripts/DSDialogs.pm b/ldap/admin/src/scripts/DSDialogs.pm index 33e6c744..049e794d 100644 --- a/ldap/admin/src/scripts/DSDialogs.pm +++ b/ldap/admin/src/scripts/DSDialogs.pm @@ -98,7 +98,7 @@ my $dsserverid = new Dialog ( my $ans = shift; my $res = $DialogManager::SAME; my $path = $self->{manager}->{setup}->{configdir} . "/slapd-" . $ans; - if ($ans !~ /^[0-9a-zA-Z_-]+$/) { + if (!isValidServerID($ans)) { $self->{manager}->alert("dialog_dsserverid_error", $ans); } elsif (-d $path) { $self->{manager}->alert("dialog_dsserverid_inuse", $ans); diff --git a/ldap/admin/src/scripts/DSMigration.pm.in b/ldap/admin/src/scripts/DSMigration.pm.in index 0e037044..070c909b 100644 --- a/ldap/admin/src/scripts/DSMigration.pm.in +++ b/ldap/admin/src/scripts/DSMigration.pm.in @@ -53,6 +53,7 @@ use Inf; # tempfiles use File::Temp qw(tempfile tempdir); +use File::Basename qw(basename); # load perldap use Mozilla::LDAP::Conn; @@ -88,6 +89,8 @@ my %ignoreOld = 'nsslapd-lockdir' => 'nsslapd-lockdir', 'nsslapd-tmpdir' => 'nsslapd-tmpdir', 'nsslapd-certdir' => 'nsslapd-certdir', + 'nsslapd-ldifdir' => 'nsslapd-ldifdir', + 'nsslapd-bakdir' => 'nsslapd-bakdir', 'nsslapd-ldapifilepath' => 'nsslapd-ldapifilepath', 'nsslapd-ldapilisten' => 'nsslapd-ldapilisten', 'nsslapd-ldapiautobind' => 'nsslapd-ldapiautobind', @@ -106,34 +109,55 @@ my %alwaysUseOld = 'aci' => 'aci' ); -my $pkgname; # global used in several different places - set in migrateDS -my $oldsroot; # global used in several different places - set in migrateDS - sub getNewDbDir { - my ($ent, $attr, $inst) = @_; + my ($ent, $attr, $mig, $inst) = @_; my %objclasses = map { lc($_) => $_ } $ent->getValues('objectclass'); my $cn = $ent->getValues('cn'); + my $oldval = $ent->getValues($attr); my $newval; + # there is one case where we want to just use the existing db directory + # that's the case where the user has moved the indexes and/or the + # transaction logs to different partitions for performance + # in that case, the old directory will not be the same as the default, + # and the directory will exist + my $olddefault = "$mig->{actualsroot}/$inst"; + if (-d $oldval and ($oldval !~ /^$olddefault/)) { + debug(2, "Keeping old value [$oldval] for attr $attr in entry ", $ent->getDN(), "\n"); + return $oldval; + } + # otherwise, just use the new default locations if ($objclasses{nsbackendinstance}) { - $newval = "@localstatedir@/lib/$pkgname/$inst/db/$cn"; + $newval = "@localstatedir@/lib/$mig->{pkgname}/$inst/db/$cn"; } elsif (lc $cn eq 'config') { - $newval = "@localstatedir@/lib/$pkgname/$inst/db"; + $newval = "@localstatedir@/lib/$mig->{pkgname}/$inst/db"; } elsif (lc $cn eq 'changelog5') { - $newval = "@localstatedir@/lib/$pkgname/$inst/cldb"; + $newval = "@localstatedir@/lib/$mig->{pkgname}/$inst/changelogdb"; } debug(2, "New value [$newval] for attr $attr in entry ", $ent->getDN(), "\n"); return $newval; } sub migrateCredentials { - my ($ent, $attr, $inst) = @_; + my ($ent, $attr, $mig, $inst) = @_; my $oldval = $ent->getValues($attr); - debug(3, "Executing migratecred -o $oldsroot/$inst -n @instconfigdir@/$inst -c $oldval . . .\n"); - my $newval = `migratecred -o $oldsroot/$inst -n @instconfigdir@/$inst -c $oldval`; + debug(3, "Executing migratecred -o $mig->{actualsroot}/$inst -n @instconfigdir@/$inst -c $oldval . . .\n"); + my $newval = `migratecred -o $mig->{actualsroot}/$inst -n @instconfigdir@/$inst -c $oldval`; debug(3, "Converted old value [$oldval] to new value [$newval] for attr $attr in entry ", $ent->getDN(), "\n"); return $newval; } +sub removensState { + my ($ent, $attr, $mig, $inst) = @_; + my $newval; + + # nsstate is binary and cannot be migrated cross platform + if (!$mig->{crossplatform}) { + $newval = $ent->getValues($attr); + } + + return $newval; +} + # these are attributes that we have to transform from # the old value to the new value (e.g. a pathname) # The key of this hash is the attribute name. The value @@ -146,110 +170,170 @@ my %transformAttr = 'nsslapd-db-logdirectory' => \&getNewDbDir, 'nsslapd-changelogdir' => \&getNewDbDir, 'nsds5replicacredentials' => \&migrateCredentials, - 'nsmultiplexorcredentials' => \&migrateCredentials + 'nsmultiplexorcredentials' => \&migrateCredentials, + 'nsstate' => \&removensState ); sub copyDatabaseDirs { my $srcdir = shift; my $destdir = shift; - if (-d $srcdir && ! -d $destdir) { + my $filesonly = shift; + if (-d $srcdir && ! -d $destdir && !$filesonly) { debug(1, "Copying database directory $srcdir to $destdir\n"); - system ("cp -p -r $srcdir $destdir") == 0 or - die "Could not copy database directory $srcdir to $destdir: $?"; + if (system ("cp -p -r $srcdir $destdir")) { + return ('error_copying_dbdir', $srcdir, $destdir, $?); + } } elsif (! -d $srcdir) { - die "Error: database directory $srcdir does not exist"; + return ("error_dbsrcdir_not_exist", $srcdir); } else { debug(1, "The destination directory $destdir already exists, copying files/dirs individually\n"); foreach my $file (glob("$srcdir/*")) { debug(3, "Copying $file to $destdir\n"); if (-f $file) { - system ("cp -p $file $destdir") == 0 or - die "Error: could not copy $file to $destdir: $!"; - } elsif (-d $file) { - system ("cp -p -r $file $destdir") == 0 or - die "Error: could not copy $file to $destdir: $!"; + if (system ("cp -p $file $destdir")) { + return ('error_copying_dbfile', $file, $destdir, $?); + } + } elsif (-d $file && !$filesonly) { + if (system ("cp -p -r $file $destdir")) { + return ('error_copying_dbdir', $file, $destdir, $?); + } } } } } -sub copyDatabases { - my $oldroot = shift; - my $inst = shift; - my $newdbdir = shift; - - # global config and instance specific config are children of this entry - my $basedbdn = normalizeDN("cn=ldbm database,cn=plugins,cn=config"); - # get the list of databases, their index and transaction log locations - my $fname = "$oldroot/$inst/config/dse.ldif"; - open( DSELDIF, "$fname" ) || die "Can't open $fname: $!"; - my $in = new Mozilla::LDAP::LDIF(*DSELDIF); - my $targetdn = normalizeDN("cn=config,cn=ldbm database,cn=plugins,cn=config"); - while (my $ent = readOneEntry $in) { - next if (!$ent->getDN()); # just skip root dse - # look for the one level children of $basedbdn - my @rdns = ldap_explode_dn($ent->getDN(), 0); - my $parentdn = normalizeDN(join(',', @rdns[1..$#rdns])); - if ($parentdn eq $basedbdn) { - my $cn = $ent->getValues('cn'); - my %objclasses = map { lc($_) => $_ } $ent->getValues('objectclass'); - if ($cn eq 'config') { # global config - debug(1, "Found ldbm database plugin config entry ", $ent->getDN(), "\n"); - my $dir = $ent->getValues('nsslapd-directory'); - my $homedir = $ent->getValues('nsslapd-db-home-directory'); - my $logdir = $ent->getValues('nsslapd-db-logdirectory'); - debug(1, "old db dir = $dir homedir = $homedir logdir = $logdir\n"); - my $srcdir = $homedir || $dir || "$oldroot/$inst/db"; - copyDatabaseDirs($srcdir, $newdbdir); - copyDatabaseDirs($logdir, $newdbdir) if ($logdir && $logdir ne $srcdir); - } elsif ($objclasses{nsbackendinstance}) { - debug(1, "Found ldbm database instance entry ", $ent->getDN(), "\n"); - my $dir = $ent->getValues('nsslapd-directory'); - # the default db instance directory is - # $oldroot/$inst/$cn - debug(1, "old instance $cn dbdir $dir\n"); - my $srcdir = $dir || "$oldroot/$inst/db/$cn"; - copyDatabaseDirs($srcdir, "$newdbdir/$cn"); - } # else just ignore for now +# migrate all of the databases in an instance +sub migrateDatabases { + my $mig = shift; # the Migration object + my $inst = shift; # the instance name (e.g. slapd-instance) + my $src = shift; # a Conn to the source + my $dest = shift; # a Conn to the dest + my $olddefault = "$mig->{actualsroot}/$inst/db"; # old default db home directory + my @errs; + + # first, look for an LDIF file in that directory with the same name as the + # database + my $foundldif; + for (glob("$mig->{oldsroot}/$inst/db/*.ldif")) { + my $dbname = basename($_, '.ldif'); + my @cmd = ("@serverdir@/$inst/ldif2db", "-n", $dbname, "-i", $_); + debug(1, "migrateDatabases: executing command ", @cmd); + if (system(@cmd)) { + return ('error_importing_migrated_db', $_, $?); } + $foundldif = 1; } - close DSELDIF; + + if ($foundldif) { + return (); # done - can do nothing else for cross-platform + } + + # if no LDIF files, just copy over the database directories + my $ent = $src->search("cn=ldbm database,cn=plugins,cn=config", "one", + "(objectclass=*)"); + if (!$ent) { + return ("error_reading_olddbconfig", $src->getErrorString()); + } + # there is one case where we want to just use the existing db directory + # that's the case where the user has moved the indexes and/or the + # transaction logs to different partitions for performance + # in that case, the old directory will not be the same as the default, + # and the directory will exist + my $olddefault = "$mig->{actualsroot}/$inst"; + do { + my $cn = $ent->getValues('cn'); + my %objclasses = map { lc($_) => $_ } $ent->getValues('objectclass'); + if ($cn eq 'config') { # global config + my $newent = $dest->search($ent->getDN(), "base", "(objectclass=*)"); + my $newdbdir = $newent->getValues('nsslapd-directory') || + "@localstatedir@/lib/$mig->{pkgname}/$inst/db"; + debug(1, "Found ldbm database plugin config entry ", $ent->getDN(), "\n"); + my $dir = $ent->getValues('nsslapd-directory'); + my $homedir = $ent->getValues('nsslapd-db-home-directory'); + my $logdir = $ent->getValues('nsslapd-db-logdirectory'); + debug(1, "old db dir = $dir homedir = $homedir logdir = $logdir\n"); + my $srcdir = $homedir || $dir || "$olddefault/db"; + if (-d $srcdir and ($srcdir !~ /^$olddefault/)) { + debug(2, "Not copying database files from [$srcdir]\n"); + } else { + # replace the old sroot value with the actual physical location on the target/dest + $srcdir =~ s/^$mig->{actualsroot}/$mig->{oldsroot}/; + if (@errs = copyDatabaseDirs($srcdir, $newdbdir, 1)) { + return @errs; + } + } + if ($logdir && ($logdir ne $srcdir)) { + if (-d $logdir and ($logdir !~ /^$olddefault/)) { + debug(2, "Not copying transaction logs from [$logdir]\n"); + } else { + # replace the old sroot value with the actual physical location on the target/dest + $newdbdir = $newent->getValues('nsslapd-db-logdirectory') || + $newdbdir; + $logdir =~ s/^$mig->{actualsroot}/$mig->{oldsroot}/; + if (@errs = copyDatabaseDirs($logdir, $newdbdir, 1)) { + return @errs; + } + } + } + } elsif ($objclasses{nsbackendinstance}) { + debug(1, "Found ldbm database instance entry ", $ent->getDN(), "\n"); + my $dir = $ent->getValues('nsslapd-directory'); + # the default db instance directory is + # $oldroot/$inst/$cn + debug(1, "old instance $cn dbdir $dir\n"); + my $srcdir = $dir || "$olddefault/db/$cn"; + my $newent = $dest->search($ent->getDN(), "base", "(objectclass=*)"); + my $newdbdir = $newent->getValues('nsslapd-directory') || + "@localstatedir@/lib/$mig->{pkgname}/$inst/db"; + if (-d $srcdir and ($srcdir !~ /^$olddefault/)) { + debug(2, "Not copying database indexes from [$srcdir]\n"); + } else { + # replace the old sroot value with the actual physical location on the target/dest + $srcdir =~ s/^$mig->{actualsroot}/$mig->{oldsroot}/; + if (@errs = copyDatabaseDirs($srcdir, "$newdbdir/$cn")) { + return @errs; + } + } + } + } while ($ent = $src->nextEntry()); + + return (); } -sub copyChangelogDB { - my $oldroot = shift; - my $inst = shift; - my $newdbdir = shift; +sub migrateChangelogs { + my $mig = shift; # the Migration object + my $inst = shift; # the instance name (e.g. slapd-instance) + my $src = shift; # a Conn to the source + my $dest = shift; # a Conn to the dest + my $olddefault = "$mig->{actualsroot}/$inst"; # old default db home directory # changelog config entry - my $cldn = normalizeDN("cn=changelog5, cn=config"); - my $fname = "$oldroot/$inst/config/dse.ldif"; - open( DSELDIF, "$fname" ) || die "Can't open $fname: $!"; - my $in = new Mozilla::LDAP::LDIF(*DSELDIF); - while (my $ent = readOneEntry $in) { - my $targetdn = normalizeDN($ent->getDN()); - if ($targetdn eq $cldn) { - my $oldcldir = $ent->getValues('nsslapd-changelogdir'); - debug(1, "old cldb dir = $oldcldir\n"); - my $srcdir = $oldcldir || "$oldroot/$inst/cldb"; - copyDatabaseDirs($srcdir, $newdbdir); - last; + my $oldent = $src->search("cn=changelog5, cn=config", "base", "(objectclass=*)"); + my $newent = $dest->search("cn=changelog5, cn=config", "base", "(objectclass=*)"); + if ($oldent and $newent) { # changelog configured + my $oldcldir = $oldent->getValues('nsslapd-changelogdir'); + if (-d $oldcldir and ($oldcldir !~ /^$olddefault/)) { + debug(2, "Not copying changelogdb from [$oldcldir]\n"); + } else { + # replace the old sroot value with the actual physical location on the target/dest + $oldcldir =~ s/^$mig->{actualsroot}/$mig->{oldsroot}/; + my $newcldir = $newent->getValues('nsslapd-changelogdir'); + copyDatabaseDirs($oldcldir, $newcldir); } } - close DSELDIF; } sub fixAttrsInEntry { - my ($ent, $inst) = @_; + my ($ent, $mig, $inst) = @_; for my $attr (keys %{$ent}) { my $lcattr = lc $attr; if ($transformAttr{$lcattr}) { - $ent->setValues($attr, &{$transformAttr{$lcattr}}($ent, $attr, $inst)); + $ent->setValues($attr, &{$transformAttr{$lcattr}}($ent, $attr, $mig, $inst)); } } } sub mergeEntries { - my ($old, $new, $inst) = @_; + my ($old, $new, $mig, $inst) = @_; my %inoldonly; # attrs in old entry but not new one my %innewonly; # attrs in new entry but not old one my @attrs; # attrs common to old and new @@ -280,7 +364,7 @@ sub mergeEntries { } elsif ($transformAttr{$lcattr}) { # only transform if the value is in the old entry if (!$innewonly{$attr}) { - $new->setValues($attr, &{$transformAttr{$lcattr}}($old, $attr, $inst)); + $new->setValues($attr, &{$transformAttr{$lcattr}}($old, $attr, $mig, $inst)); } } elsif ($cn eq "internationalization plugin" and $lcattr eq "nsslapd-pluginarg0") { next; # use the new value of this path name @@ -294,41 +378,72 @@ sub mergeEntries { } } -sub mergeDseLdif { - my $oldroot = shift; - my $inst = shift; - my $ent; + +my @allattrlist = ('*', 'aci', 'createTimestamp', 'creatorsName', + 'modifyTimestamp', 'modifiersName'); + +sub getAllEntries { + my $conn = shift; + my $href = shift; + my $aref = shift; + + # these are the special DSEs for which we only need ACIs + for my $dn ("", "cn=monitor", "cn=config") { + my $scope = $dn ? "sub" : "base"; + my @attrlist; + if ($dn eq "cn=config") { + @attrlist = @allattrlist; + } else { + @attrlist = qw(aci); + } + my $ent = $conn->search($dn, $scope, "(objectclass=*)", 0, @attrlist); + next if (!$ent or ($conn->getErrorCode() eq 32)); + if ($conn->getErrorCode()) { + return ('error_reading_entry', $dn, $conn->getErrorString()); + } + do { + my $ndn = normalizeDN($ent->getDN()); + $href->{$ndn} = $ent; + push @{$aref}, $ndn; + } while ($ent = $conn->nextEntry()); + } + + return (); +} + +# these entries cannot be migrated if doing cross platform +my %noCrossPlatformDN = ( + 'cn=uniqueid generator,cn=config' => 'cn=uniqueid generator,cn=config' +); + +sub mergeConfigEntries { + my $mig = shift; # the Migration object + my $inst = shift; # the instance name (e.g. slapd-instance) + my $src = shift; # a Conn to the source + my $dest = shift; # a Conn to the dest # first, read in old file my %olddse; # map of normalized DN to Entry my @olddns; # the DNs in their original order - my $fname = "$oldroot/$inst/config/dse.ldif"; - open( OLDDSELDIF, $fname ) || die "Can't open $fname: $!"; - my $in = new Mozilla::LDAP::LDIF(*OLDDSELDIF); - while ($ent = readOneEntry $in) { - my $dn = normalizeDN($ent->getDN()); - push @olddns, $dn; - $olddse{$dn} = $ent; + my @errs; + if (@errs = getAllEntries($src, \%olddse, \@olddns)) { + return @errs; } - close OLDDSELDIF; # next, read in new file my %newdse; # map of normalized DN to Entry + my @allnewdns; my @newdns; # the DNs in their original order that are not in olddns - $fname = "@instconfigdir@/$inst/dse.ldif"; - open( NEWDSELDIF, $fname ) || die "Can't open $fname: $!"; - $in = new Mozilla::LDAP::LDIF(*NEWDSELDIF); - while ($ent = readOneEntry $in) { - my $dn = normalizeDN($ent->getDN()); - $newdse{$dn} = $ent; - if (! exists $olddse{$dn}) { - push @newdns, $dn; + if (@errs = getAllEntries($dest, \%newdse, \@allnewdns)) { + return @errs; + } + + for my $ndn (@allnewdns) { + if (! exists $olddse{$ndn}) { + push @newdns, $ndn; } } - close NEWDSELDIF; - # temp file for new, merged dse.ldif - my ($dsefh, $tmpdse) = tempfile(SUFFIX => '.ldif'); # now, compare entries # if the entry exists in the old tree but not the new, add it # if the entry exists in the new tree but not the old, delete it @@ -339,58 +454,144 @@ sub mergeDseLdif { for my $dn (@olddns, @newdns) { my $oldent = $olddse{$dn}; my $newent = $newdse{$dn}; - my $outputent; - if ($oldent && !$newent) { + my $op; + my $rc = 1; + if ($mig->{crossplatform} && $noCrossPlatformDN{$dn}) { + debug(1, "Cannot migrate the entry $dn - skipping\n"); + next; + } elsif ($oldent && !$newent) { # may have to fix up some values in the old entry - fixAttrsInEntry($oldent, $inst); - # output $oldent - $outputent = $oldent; + fixAttrsInEntry($oldent, $mig, $inst); + $rc = $dest->add($oldent); + $op = "add"; } elsif (!$oldent && $newent) { - next if ($dn =~ /o=deleteAfterMigration/i); - # output $newent - $outputent = $newent; + if ($dn =~ /o=deleteAfterMigration/i) { + $rc = $dest->delete($dn); + $op = "delete"; + } else { + # do nothing - no change to entry + } } else { #merge # $newent will contain the merged entry - mergeEntries($oldent, $newent, $inst); - $outputent = $newent; + mergeEntries($oldent, $newent, $mig, $inst); + $rc = $dest->update($newent); + $op = "update"; } - # special fix for rootDSE - perldap doesn't like "" for a dn - if (! $outputent->getDN()) { - my $ary = $outputent->getLDIFrecords(); - shift @$ary; # remove "dn" - shift @$ary; # remove the empty dn value - print $dsefh "dn:\n"; - print $dsefh (Mozilla::LDAP::LDIF::pack_LDIF (78, $ary), "\n"); - } else { - Mozilla::LDAP::LDIF::put_LDIF($dsefh, 78, $outputent); + + if (!$rc) { + return ('error_updating_merge_entry', $op, $dn, $dest->getErrorString()); + } + } + + return (); +} + +my %deletedschema = ( + '50ns-calendar' => '50ns-calendar.ldif', + '50ns-compass' => '50ns-compass.ldif', + '50ns-delegated-admin' => '50ns-delegated-admin.ldif', + '50ns-legacy' => '50ns-legacy.ldif', + '50ns-mail' => '50ns-mail.ldif', + '50ns-mcd-browser' => '50ns-mcd-browser.ldif', + '50ns-mcd-config' => '50ns-mcd-config.ldif', + '50ns-mcd-li' => '50ns-mcd-li.ldif', + '50ns-mcd-mail' => '50ns-mcd-mail.ldif', + '50ns-media' => '50ns-media.ldif', + '50ns-mlm' => '50ns-mlm.ldif', + '50ns-msg' => '50ns-msg.ldif', + '50ns-netshare' => '50ns-netshare.ldif', + '50ns-news' => '50ns-news.ldif', + '50ns-proxy' => '50ns-proxy.ldif', + '50ns-wcal' => '50ns-wcal.ldif', + '51ns-calendar' => '51ns-calendar.ldif' +); + +sub migrateSchema { + my $mig = shift; # the Migration object + my $inst = shift; # the instance name (e.g. slapd-instance) + my $src = shift; # a Conn to the source + my $dest = shift; # a Conn to the dest + + my $cfgent = $dest->search("cn=config", "base", "(objectclass=*)"); + my $newschemadir = $cfgent->getValues('nsslapd-schemadir') || + "$mig->{configdir}/$inst/schema"; + my %newschema = map {basename($_, '.ldif') => $_} glob("$newschemadir/*.ldif"); + delete $newschema{"99user"}; # always copy this one + for (glob("$mig->{oldsroot}/$inst/config/schema/*.ldif")) { + my $fname = basename($_, '.ldif'); + next if ($deletedschema{$fname}); # don't copy deleted schema + next if ($newschema{$fname}); # use new version + if (system("cp -p $_ $newschemadir")) { + return ("error_migrating_schema", $_, $!); } } - close $dsefh; - return $tmpdse; + return (); +} + +sub migrateDSInstance { + my $mig = shift; # the Migration object + my $inst = shift; # the instance name (e.g. slapd-instance) + my $src = shift; # a Conn to the source + my $dest = shift; # a Conn to the dest + + my @errs; + # first, merge dse ldif + if (@errs = mergeConfigEntries($mig, $inst, $src, $dest)) { + return @errs; + } + + # next, grab the old schema + if (@errs = migrateSchema($mig, $inst, $src, $dest)) { + return @errs; + } + + # next, the databases + if (@errs = migrateDatabases($mig, $inst, $src, $dest)) { + return @errs; + } + + # next, the changelogs + if (!$mig->{crossplatform}) { + if (@errs = migrateChangelogs($mig, $inst, $src, $dest)) { + return @errs; + } + } + + # next, the security files + my $cfgent = $dest->search("cn=config", "base", "(objectclass=*)"); + my $newcertdir = $cfgent->getValues("nsslapd-certdir") || + "@instconfigdir@/$inst"; + $mig->migrateSecurityFiles($inst, $newcertdir); + + return @errs; } sub migrateDS { my $mig = shift; - $pkgname = $mig->{pkgname}; # set globals - $oldsroot = $mig->{oldsroot}; # set globals my @errs; # for each instance foreach my $inst (@{$mig->{instances}}) { - if (-f "@instconfigdir@/$inst/dse.ldif") { - $mig->msg($WARN, 'instance_already_exists', "@instconfigdir@/$inst/dse.ldif"); + if (-f "$mig->{configdir}/$inst/dse.ldif") { + $mig->msg($WARN, 'instance_already_exists', "$mig->{configdir}/$inst/dse.ldif"); next; } - # set instance specific defaults - my $newdbdir = "@localstatedir@/lib/$pkgname/$inst/db"; - my $newcertdir = "@instconfigdir@/$inst"; - my $newcldbdir = "@localstatedir@/lib/$pkgname/$inst/cldb"; + + # you could theoretically make this work with either a remote source or + # remote dest + # $mig->{inf} would contain an entry for each instance e.g. + # $mig->{inf}->{$inst} + # each instance specific entry would contain a {General} and a {slapd} + # all the information necessary to open an LDAP::Conn to the server + # if the source, you could also change createInfFromConfig to read + # the info from the Conn (or FileConn) that's needed to create the + # instance on the dest # extract the information needed for ds_newinst.pl - my $configdir = "$oldsroot/$inst/config"; - my $inf = createInfFromConfig($configdir, $inst, \@errs); - debug(2, "Using inffile $inf->{filename} created from $configdir\n"); + my $oldconfigdir = "$mig->{oldsroot}/$inst/config"; + my $inf = createInfFromConfig($oldconfigdir, $inst, \@errs); + debug(2, "Using inffile $inf->{filename} created from $oldconfigdir\n"); if (@errs) { $mig->msg(@errs); return 0; @@ -407,31 +608,16 @@ sub migrateDS { $mig->msg('created_dsinstance', $output); } - # copy over the files/directories - # copy the databases - copyDatabases($oldsroot, $inst, $newdbdir); + my $src = new FileConn("$oldconfigdir/dse.ldif", 1); # read-only + my $dest = new FileConn("$mig->{configdir}/$inst/dse.ldif"); - # copy the security related files - $mig->migrateSecurityFiles($inst, $newcertdir); - - # copy the repl changelog database - copyChangelogDB($oldsroot, $inst, $newcldbdir); - - # merge the old info into the new dse.ldif - my $tmpdse = mergeDseLdif($oldsroot, $inst); - - # get user/group of new dse - my ($dev, $ino, $mode, $uid, $gid, @rest) = stat "@instconfigdir@/$inst/dse.ldif"; - # save the original new dse.ldif - system("cp -p @instconfigdir@/$inst/dse.ldif @instconfigdir@/$inst/dse.ldif.premigrate"); - # copy the new one - system("cp $tmpdse @instconfigdir@/$inst/dse.ldif"); - # change owner/group - chmod $mode, "@instconfigdir@/$inst/dse.ldif"; - chown $uid, $gid, "@instconfigdir@/$inst/dse.ldif"; - - # remove the temp one - unlink($tmpdse); + @errs = migrateDSInstance($mig, $inst, $src, $dest); + $src->close(); + $dest->close(); + if (@errs) { + $mig->msg(@errs); + return 0; + } } return 1; diff --git a/ldap/admin/src/scripts/FileConn.pm b/ldap/admin/src/scripts/FileConn.pm index c777b156..ea68d41f 100644 --- a/ldap/admin/src/scripts/FileConn.pm +++ b/ldap/admin/src/scripts/FileConn.pm @@ -54,10 +54,12 @@ require Exporter; sub new { my $class = shift; my $filename = shift; + my $readonly = shift; my $self = {}; $self = bless $self, $class; + $self->{readonly} = $readonly; $self->read($filename); return $self; @@ -103,8 +105,12 @@ sub iterate { my $context = shift; my $suppress = shift; my $ndn = normalizeDN($dn); - my $children = $self->{$ndn}->{children}; - if (($scope != LDAP_SCOPE_ONELEVEL) && $self->{$ndn}->{data} && !$suppress) { + my $children; + if (exists($self->{$ndn}) and exists($self->{$ndn}->{children})) { + $children = $self->{$ndn}->{children}; + } + if (($scope != LDAP_SCOPE_ONELEVEL) && exists($self->{$ndn}) && + exists($self->{$ndn}->{data}) && $self->{$ndn}->{data} && !$suppress) { &{$callback}($self->{$ndn}->{data}, $context); } @@ -146,7 +152,7 @@ sub write { $filename = $self->{filename}; } - if (!$self->{filename}) { + if (!$self->{filename} or $self->{readonly}) { return; } @@ -181,8 +187,14 @@ sub printError print "$str ", $self->getErrorString(), "\n"; } +sub DESTROY { + my $self = shift; + $self->close(); +} + sub close { my $self = shift; + return if ($self->{readonly}); $self->write(); } @@ -280,7 +292,7 @@ sub search { $self->{entries} = []; my $ndn = normalizeDN($basedn); - if (!exists($self->{$ndn})) { + if (!exists($self->{$ndn}) or !exists($self->{$ndn}->{data})) { $self->setErrorCode(LDAP_NO_SUCH_OBJECT); return undef; } @@ -308,12 +320,22 @@ sub add { my $parentdn = getParentDN($dn); my $nparentdn = normalizeDN($parentdn); + $self->setErrorCode(0); + # special case of root DSE + if (!$ndn and exists($self->{$ndn}) and + !exists($self->{$ndn}->{data})) { + $self->{$ndn}->{data} = $entry; + $self->write(); + return 1; + } + if (exists($self->{$ndn})) { $self->setErrorCode(LDAP_ALREADY_EXISTS); return 0; } - if ($nparentdn && !exists($self->{$nparentdn})) { + + if ($ndn && $nparentdn && !exists($self->{$nparentdn})) { $self->setErrorCode(LDAP_NO_SUCH_OBJECT); return 0; } @@ -321,7 +343,10 @@ sub add { # data is the actual Entry # children is the array ref of the one level children of this dn $self->{$ndn}->{data} = $entry; - push @{$self->{$nparentdn}->{children}}, $self->{$ndn}; + # don't add parent to list of children + if ($nparentdn ne $ndn) { + push @{$self->{$nparentdn}->{children}}, $self->{$ndn}; + } return 1; } @@ -339,6 +364,7 @@ sub update { } $self->{$ndn}->{data} = $entry; + $self->write(); return 1; } @@ -370,20 +396,23 @@ sub delete { my $parentdn = getParentDN($dn); my $nparentdn = normalizeDN($parentdn); # delete this node from its parent - for (my $ii = 0; $ii < @{$self->{$nparentdn}->{children}}; ++$ii) { - # find matching hash ref in parent's child list - if ($self->{$nparentdn}->{children}->[$ii] eq $self->{$ndn}) { - # remove that element from the array - splice @{$self->{$nparentdn}->{children}}, $ii, 1; - # done - should only ever be one matching child - last; + if ($ndn ne $nparentdn) { + for (my $ii = 0; $ii < @{$self->{$nparentdn}->{children}}; ++$ii) { + # find matching hash ref in parent's child list + if ($self->{$nparentdn}->{children}->[$ii] eq $self->{$ndn}) { + # remove that element from the array + splice @{$self->{$nparentdn}->{children}}, $ii, 1; + # done - should only ever be one matching child + last; + } } } # delete this node delete $self->{$ndn}; - return 0; + $self->write(); + return 1; } 1; diff --git a/ldap/admin/src/scripts/Migration.pm.in b/ldap/admin/src/scripts/Migration.pm.in index 94123d37..21122709 100644 --- a/ldap/admin/src/scripts/Migration.pm.in +++ b/ldap/admin/src/scripts/Migration.pm.in @@ -101,16 +101,24 @@ options: --version Print the version and exit --debug Turn on debugging --oldsroot The old server root directory to migrate from - --actualsroot This is the old location of the old server root. See below. + --actualsroot This is the old location of the old server root. + See below. --silent Use silent setup - no user input - --file=name Use the file 'name' in .inf format to supply the default answers - --keepcache Do not delete the temporary .inf file generated by this program - --logfile Log migration messages to this file - otherwise, a temp file will be used - --instance By default, all directory server instances will be migrated. You can use - this argument to specify one or more (e.g. -i slapd-foo -i slapd-bar) if - you do not want to migrate all of them. -For all options, you can also use the short name e.g. -h, -d, etc. For the -d argument, -specifying it more than once will increase the debug level e.g. -ddddd + --file=name Use the file 'name' in .inf format to supply the + default answers + --keepcache Do not delete the temporary .inf file generated by + this program + --logfile Log migration messages to this file - otherwise, a temp + file will be used + --instance By default, all directory server instances will be + migrated. You can use this argument to specify one + or more (e.g. -i slapd-foo -i slapd-bar) if you do + not want to migrate all of them. + --cross See below. + +For all options, you can also use the short name e.g. -h, -d, etc. +For the -d argument, specifying it more than once will increase the +debug level e.g. -ddddd args: You can supply default .inf data in this format: @@ -119,7 +127,18 @@ e.g. General.FullMachineName=foo.example.com or "slapd.Suffix=dc=example, dc=com" -Values passed in this manner will override values in an .inf file given with the -f argument. +Values passed in this manner will override values in an .inf file +given with the -f argument. If you need to specify the cleartext +directory manager password (e.g. in order to do remote migration), +you must specify the password for each instance in a section whose +name is the instance name e.g. + [slapd-ldap1] + RootDNPwd=ldap1password + [slapd-ldap2] + RootDNPwd=ldap2password +or on the command line like this: + command ... slapd-ldap1.RootDNPwd=ldap1password \ + slapd-ldap2.RootDNPwd=ldap2password ... actualsroot: This is used when you must migrate from one machine to another. The @@ -142,13 +161,36 @@ as the --actualsroot argument, and use /migration/opt/myds for the --oldsroot argument. That is, the oldsroot is the physical location of the files on disk. The actualsroot is the old value of the server root on the source machine. + +cross: +Also known as crossplatform, or 'c', or 'x'. +This is when the source machine is a different architecture than the +destination machine. In this case, only certain data will be available +for migration. Changelog information will not be migrated, and replicas +will need to be reinitialized (if migrating masters or hubs). This type +of migration requires that all of your old databases have been dumped +to LDIF format, and the LDIF file must be in the default database directory +(usually /opt/@brand@-ds/slapd-instance/db), and the LDIF file must have +the same name as the database instance directory, with a ".ldif". For +example, if you have + /opt/@brand@-ds/slapd-instance/db/userRoot/ and + /opt/@brand@-ds/slapd-instance/db/NetscapeRoot/ +you must first use db2ldif to export these databases to LDIF e.g. + cd /opt/@brand@-ds/slapd-instance + ./db2ldif -n userRoot -a /opt/@brand@-ds/slapd-instance/db/userRoot.ldif and + ./db2ldif -n NetscapeRoot -a /opt/@brand@-ds/slapd-instance/db/NetscapeRoot.ldif + +Then you must somehow make your old server root directory available on +the destination machine, either by creating a tar archive on the source +and copying it to the destination, or by network mounting the source +directory on the destination machine. EOF } sub init { my $self = shift; $self->{res} = shift; - my ($silent, $inffile, $keep, $preonly, $logfile, $oldsroot, $actualsroot); + my ($silent, $inffile, $keep, $preonly, $logfile, $oldsroot, $actualsroot, $crossplatform); my @instances; GetOptions('help|h|?' => sub { VersionMessage(); HelpMessage(); exit 0 }, @@ -161,6 +203,7 @@ sub init { 'logfile|l=s' => \$logfile, 'oldsroot|o=s' => \$oldsroot, 'actualsroot|a=s' => \$actualsroot, + 'crossplatform|cross|c|x' => \$crossplatform, 'instance|i=s' => \@instances ); @@ -180,6 +223,7 @@ sub init { $self->{keep} = $keep; $self->{preonly} = $preonly; $self->{logfile} = $logfile; + $self->{crossplatform} = $crossplatform; $self->{log} = new SetupLog($self->{logfile}, "migrate"); # if user supplied inf file, use that to initialize if (defined($self->{inffile})) { @@ -220,7 +264,12 @@ sub init { glob("$self->{oldsroot}/slapd-*"); } - die "No instances found to migrate" unless (@instances); + if (!@instances) { + $self->msg($FATAL, "error_no_instances", $self->{oldsroot}); + VersionMessage(); + HelpMessage(); + exit 1; + } $self->{instances} = \@instances; } diff --git a/ldap/admin/src/scripts/Util.pm.in b/ldap/admin/src/scripts/Util.pm.in index b8b1528f..364e9115 100644 --- a/ldap/admin/src/scripts/Util.pm.in +++ b/ldap/admin/src/scripts/Util.pm.in @@ -47,10 +47,12 @@ require Exporter; @ISA = qw(Exporter); @EXPORT = qw(portAvailable getAvailablePort isValidDN addSuffix getMappedEntries process_maptbl check_and_add_entry getMappedEntries - getHashedPassword debug createDSInstance createInfFromConfig); + getHashedPassword debug createDSInstance createInfFromConfig + isValidServerID); @EXPORT_OK = qw(portAvailable getAvailablePort isValidDN addSuffix getMappedEntries process_maptbl check_and_add_entry getMappedEntries - getHashedPassword debug createDSInstance createInfFromConfig); + getHashedPassword debug createDSInstance createInfFromConfig + isValidServerID); use strict; @@ -102,6 +104,40 @@ sub isValidDN { return ($dn =~ /^[0-9a-zA-Z_-]+=.*$/); } +sub isValidServerID { + my $servid = shift; + my $validchars = '#%,.:\w@_-'; + return $servid =~ /^[$validchars]+$/o; +} + +sub isValidUser { + my $user = shift; + # convert numeric uid to string + my $strans = $user; + if ($user =~ /^\d+$/) { # numeric - convert to string + $strans = getpwuid $user; + if (!$strans) { + return ("dialog_ssuser_error", $user); + } + } + if ($> != 0) { # if not root, the user must be our uid + my $username = getlogin; + if ($strans ne $username) { + return ("dialog_ssuser_must_be_same", $username); + } + } else { # user is root - verify id + my $nuid = getpwnam $strans; + if (!defined($nuid)) { + return ("dialog_ssuser_error", $user); + } + if (!$nuid) { + return ("dialog_ssuser_root_warning"); + } + } + + return (); +} + # delete the subtree starting from the passed entry sub delete_all { diff --git a/ldap/admin/src/scripts/migrate-ds.res b/ldap/admin/src/scripts/migrate-ds.res index e0eec698..f5cfef15 100644 --- a/ldap/admin/src/scripts/migrate-ds.res +++ b/ldap/admin/src/scripts/migrate-ds.res @@ -1,4 +1,13 @@ begin_ds_migration = Beginning migration of directory server instances in %s . . .\n end_ds_migration = Directory server migration is complete. Please check output and log files for details.\n migration_exiting = Exiting . . .\nLog file is '%s'\n\n -instance_already_exists = The target directory server instance already exists at %s. Skipping migration.\n\ +instance_already_exists = The target directory server instance already exists at %s. Skipping migration. Note that if you want to migrate the old instance you will have to first remove the new one of the same name.\n\n +error_reading_entry = Could not read the entry '%s'. Error: %s\n +error_updating_merge_entry = Could not %s the migrated entry '%s' in the target directory server. Error: %s\n +error_importing_migrated_db = Could not import the LDIF file '%s' for the migrated database. Error: %s. Please check the directory server error log for more details.\n +error_reading_olddbconfig = Could not read the old database configuration information. Error: %s\n +error_migrating_schema = Could not copy old schema file '%s'. Error: %s\n +error_copying_dbdir = Could not copy database directory '%s' to '%s'. Error: %s\n +error_copying_dbfile = Could not copy database file '%s' to '%s'. Error: %s\n +error_dbsrcdir_not_exist = Could not copy from the database source directory '%s' because it does not exist. Please check your configuration.\n +error_no_instances = Could not find any instances in the old directory '%s' to migrate.\n diff --git a/ldap/admin/src/scripts/setup-ds.pl.in b/ldap/admin/src/scripts/setup-ds.pl.in index f52cd169..b455a579 100644 --- a/ldap/admin/src/scripts/setup-ds.pl.in +++ b/ldap/admin/src/scripts/setup-ds.pl.in @@ -42,6 +42,7 @@ use lib '@perldir@'; use strict; use Setup; +use SetupLog; use Inf; use Resource; use DialogManager; @@ -78,4 +79,12 @@ if ($rc) { $setup->msg('created_dsinstance', $output); } -$setup->doExit(); +END { + if ($setup) { + if (!$setup->{keep}) { + unlink $setup->{inffile}; + } + + $setup->doExit(); + } +} diff --git a/ldap/admin/src/scripts/setup-ds.res.in b/ldap/admin/src/scripts/setup-ds.res.in index 4b8982e1..329a7c24 100644 --- a/ldap/admin/src/scripts/setup-ds.res.in +++ b/ldap/admin/src/scripts/setup-ds.res.in @@ -95,3 +95,12 @@ error_mapping_token_ldiftmpl = The entry '%s' in LDIF file '%s' contains a token error_deleteall_entries = Error deleting entry '%s' and all children. Error: %s\n error_adding_entry = Error adding entry '%s'. Error: %s\n error_updating_entry = Error updating entry '%s'. Error: %s\n + + +error_invalid_param = The parameter '%s' has an invalid value '%s'.\n +error_port_available = The port number '%s' is not available for use. This may be due to an\ +invalid port number, or the port already being in use by another\ +program, or low port restriction. Please choose another value for\ +ServerPort. Error: $!\n +error_invalid_serverid = The ServerIdentifier '%s' contains invalid characters. It must\ +contain only alphanumeric characters and the following: #%,.:@_-\n |