| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170 |
- # BEGIN COPYRIGHT BLOCK
- # 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; version 2 of the License.
- #
- # 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 should have received a copy of the GNU General Public License along with
- # this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
- # Place, Suite 330, Boston, MA 02111-1307 USA.
- #
- # In addition, as a special exception, Red Hat, Inc. gives You the additional
- # right to link the code of this Program with code not covered under the GNU
- # General Public License ("Non-GPL Code") and to distribute linked combinations
- # including the two, subject to the limitations in this paragraph. Non-GPL Code
- # permitted under this exception must only link to the code of this Program
- # through those well defined interfaces identified in the file named EXCEPTION
- # found in the source code files (the "Approved Interfaces"). The files of
- # Non-GPL Code may instantiate templates or use macros or inline functions from
- # the Approved Interfaces without causing the resulting work to be covered by
- # the GNU General Public License. Only Red Hat, Inc. may make changes or
- # additions to the list of Approved Interfaces. You must obey the GNU General
- # Public License in all respects for all of the Program code and other code used
- # in conjunction with the Program except the Non-GPL Code covered by this
- # exception. If you modify this file, you may extend this exception to your
- # version of the file, but you are not obligated to do so. If you do not wish to
- # provide this exception without modification, you must delete this exception
- # statement from your version and license this file solely under the GPL without
- # exception.
- #
- #
- # Copyright (C) 2007 Red Hat, Inc.
- # All rights reserved.
- # END COPYRIGHT BLOCK
- #
- ###########################
- #
- # This perl module provides a way to set up a new installation after
- # the binaries have already been extracted. This is typically after
- # using native packaging support to install the package e.g. RPM,
- # pkgadd, depot, etc. This script will show the license, readme,
- # dsktune, then run the usual setup pre and post installers.
- #
- ##########################
- package DSMigration;
- use Migration;
- use DSUtil;
- use Inf;
- use DSCreate;
- # tempfiles
- use File::Temp qw(tempfile tempdir);
- use File::Basename qw(basename);
- # absolute path handling
- use Cwd qw(realpath);
- # load perldap
- use Mozilla::LDAP::Conn;
- use Mozilla::LDAP::Utils qw(normalizeDN);
- use Mozilla::LDAP::API qw(ldap_explode_dn);
- use Mozilla::LDAP::LDIF;
- use Carp;
- use Exporter;
- @ISA = qw(Exporter);
- @EXPORT = qw(migrateDS);
- @EXPORT_OK = qw(migrateDS);
- use strict;
- use SetupLog;
- # these are the attributes for which we will always use
- # the new value, or which do not apply anymore
- # for the next major release e.g. when we support migration from the
- # current release 1.1.x to 1.2 or 2.0, the old version number will
- # become quite important for migration - for example, when migrating
- # from older than 1.1 to 1.1.x, we need to add the attributes in the
- # table below to the new entry because the attribute didn't exist
- # at all in the old server version - however, when migrating from
- # e.g. 1.1.x to 2.0, we must preserve the old value - this means
- # if the user has deleted the attribute from the entry, we must
- # "migrate" that deletion by removing the attribute from the new
- # entry
- my %ignoreOld =
- (
- 'nsslapd-errorlog' => 'nsslapd-errorlog',
- 'nsslapd-accesslog' => 'nsslapd-accesslog',
- 'nsslapd-auditlog' => 'nsslapd-auditlog',
- 'nskeyfile' => 'nsKeyfile',
- 'nscertfile' => 'nsCertfile',
- 'nsslapd-pluginpath' => 'nsslapd-pluginPath',
- 'nsslapd-plugintype' => 'nsslapd-pluginType',
- 'nsslapd-pluginversion' => 'nsslapd-pluginVersion',
- 'nsslapd-plugin-depends-on-named' => 'nsslapd-plugin-depends-on-named',
- # these are new attrs that we should just pass through
- 'nsslapd-allow-unauthenticated-binds' => 'nsslapd-allow-unauthenticated-binds',
- 'nsslapd-allow-anonymous-access' => 'nsslapd-allow-anonymous-access',
- 'nsslapd-saslpath' => 'nsslapd-saslpath',
- 'nsslapd-rundir' => 'nsslapd-rundir',
- 'nsslapd-schemadir' => 'nsslapd-schemadir',
- 'nsslapd-lockdir' => 'nsslapd-lockdir',
- 'nsslapd-tmpdir' => 'nsslapd-tmpdir',
- 'nsslapd-certdir' => 'nsslapd-certdir',
- 'nsslapd-ldifdir' => 'nsslapd-ldifdir',
- 'nsslapd-bakdir' => 'nsslapd-bakdir',
- 'nsslapd-instancedir' => 'nsslapd-instancedir',
- 'nsslapd-ldapifilepath' => 'nsslapd-ldapifilepath',
- 'nsslapd-ldapilisten' => 'nsslapd-ldapilisten',
- 'nsslapd-ldapiautobind' => 'nsslapd-ldapiautobind',
- 'nsslapd-ldapimaprootdn' => 'nsslapd-ldapimaprootdn',
- 'nsslapd-ldapimaptoentries' => 'nsslapd-ldapimaptoentries',
- 'nsslapd-ldapiuidnumbertype' => 'nsslapd-ldapiuidnumbertype',
- 'nsslapd-ldapigidnumbertype' => 'nsslapd-ldapigidnumbertype',
- 'nsslapd-ldapientrysearchbase' => 'nsslapd-ldapientrysearchbase',
- 'nsslapd-ldapiautodnsuffix' => 'nsslapd-ldapiautodnsuffix',
- 'numsubordinates' => 'numSubordinates',
- # for these, we just want to use the default values, even if they were
- # set in 7.1 or later
- 'nsslapd-db-private-import-mem' => 'nsslapd-db-private-import-mem',
- 'nsslapd-import-cache-autosize' => 'nsslapd-import-cache-autosize',
- # nsslapd-allidsthreshold does not exist anymore
- # the analogous concept is nsslapd-idlistscanlimit for searches
- 'nsslapd-allidsthreshold' => 'nsslapd-allidsthreshold'
- );
- # these are the obsolete entries we do not migrate
- my %ignoreOldEntries =
- (
- 'cn=presence,cn=plugins,cn=config' => 'cn=presence,cn=plugins,cn=config',
- 'cn=aim presence,cn=presence,cn=plugins,cn=config' => 'cn=aim presence,cn=presence,cn=plugins,cn=config',
- 'cn=icq presence,cn=presence,cn=plugins,cn=config' => 'cn=icq presence,cn=presence,cn=plugins,cn=config',
- 'cn=yahoo presence,cn=presence,cn=plugins,cn=config' => 'cn=yahoo presence,cn=presence,cn=plugins,cn=config'
- );
- # these are the attributes for which we will always use
- # the old value
- my %alwaysUseOld =
- (
- 'aci' => 'aci'
- );
- sub getDBVERSION {
- my $olddbdir = shift;
- my $data = shift;
- open DBVERSION, "$olddbdir/DBVERSION" or
- return ('error_reading_dbversion', $olddbdir, $!);
- my $line = <DBVERSION>;
- close DBVERSION;
- chomp($line);
- @{$data} = split("/", $line);
- return ();
- }
- sub isOldDatabase {
- my $olddbdir = shift;
- my $errs = shift; # array ref
- # check old DBVERSION file
- my @verinfo;
- if (@{$errs} = getDBVERSION($olddbdir, \@verinfo)) {
- return 0;
- }
- if ((($verinfo[0] =~ /^netscape/i) or ($verinfo[0] =~ /^iplanet/i)) and
- (($verinfo[1] =~ /^6/) or ($verinfo[1] =~ /^5/) or ($verinfo[1] =~ /^4/))) {
- return 1;
- }
- return 0;
- }
- sub getNewDbDir {
- my ($ent, $attr, $mig, $inst) = @_;
- my $newval;
- my %objclasses = map { lc($_) => $_ } $ent->getValues('objectclass');
- my $cn = $ent->getValues('cn');
- # 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
- # for cross platform, we should just use the new default location
- if (!$mig->{crossplatform}) {
- my $oldval = $ent->getValues($attr);
- my $absoldval = realpath($oldval) || $oldval;
- my $olddefault = "$mig->{actualsroot}/$inst";
- if (-d $absoldval and ($absoldval !~ /^$olddefault/)) {
- debug(2, "Keeping old value [$absoldval] for attr $attr in entry ", $ent->getDN(), "\n");
- return $oldval;
- }
- }
- # otherwise, just use the new default locations
- if ("@with_fhs_opt@") {
- if ($objclasses{nsbackendinstance}) {
- $newval = "@localstatedir@/$mig->{pkgname}/$inst/db/$cn";
- } elsif (lc $cn eq 'config') {
- $newval = "@localstatedir@/$mig->{pkgname}/$inst/db";
- } elsif (lc $cn eq 'changelog5') {
- $newval = "@localstatedir@/$mig->{pkgname}/$inst/changelogdb";
- }
- } else {
- if ($objclasses{nsbackendinstance}) {
- $newval = "@localstatedir@/lib/$mig->{pkgname}/$inst/db/$cn";
- } elsif (lc $cn eq 'config') {
- $newval = "@localstatedir@/lib/$mig->{pkgname}/$inst/db";
- } elsif (lc $cn eq 'changelog5') {
- $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, $mig, $inst) = @_;
- my $oldval = $ent->getValues($attr);
- my $qoldval = shellEscape($oldval);
- # Older versions of the server on x86 systems and other systems that do not use network byte order
- # stored the credentials incorrectly. The first step is to determine if this is the case. We
- # migrate using the same server root to see if we get the same output as we input.
- debug(3, "In migrateCredentials - see how old credentials were encoded.\n");
- my $testval = `@bindir@/migratecred -o $mig->{actualsroot}/$inst -n $mig->{actualsroot}/$inst -c $qoldval`;
- chomp($testval);
- if ($testval ne $oldval) { # need to turn on the special flag
- debug(3, "Credentials not encoded correctly. oldval $oldval not equal to testval $testval. The value will be re-encoded correctly.\n");
- $ENV{MIGRATE_BROKEN_PWD} = "1"; # decode and re-encode correctly
- }
-
- debug(3, "Executing @bindir@/migratecred -o $mig->{actualsroot}/$inst -n @instconfigdir@/$inst -c $qoldval . . .\n");
- my $newval = `@bindir@/migratecred -o $mig->{actualsroot}/$inst -n @instconfigdir@/$inst -c $qoldval`;
- chomp($newval);
- delete $ENV{MIGRATE_BROKEN_PWD}; # clear the flag, if set
- 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;
- }
- sub migIdlSwitch {
- my ($ent, $attr, $mig, $inst) = @_;
- my $newval;
- # if doing cross platform migration, just use the default value for
- # nsslapd-idl-switch
- # if not doing cross platform, meaning we just use the existing
- # database binaries, we must preserve whatever the old value is
- # unless migrating from 6.21 or earlier, in which case we must
- # be migrating from LDIF, and must use the new idl switch
- if (!$mig->{crossplatform}) {
- # the given entry is the old entry - see if it has the nsslapd-directory
- my $olddbdir = $ent->getValues('nsslapd-db-home-directory') ||
- $ent->getValues('nsslapd-directory') ||
- "$mig->{actualsroot}/$inst/db"; # old default db home directory
- # replace the old sroot value with the actual physical location on the target/dest
- $olddbdir =~ s/^$mig->{actualsroot}/$mig->{oldsroot}/;
- my @errs;
- my $isold = isOldDatabase($olddbdir, \@errs);
- if (@errs) {
- $mig->msg($FATAL, @errs);
- return $newval; # use default new value
- } elsif ($isold) {
- debug(3, "The database in $olddbdir is too old to migrate the idl switch setting\n");
- return $newval; # use default new value
- }
- # else the database could be in the new format already - preserve
- # the user's old value
- $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
- # is an anonymous sub which takes two arguments - the entry
- # and the old value. The return value of the sub is
- # the new value
- my %transformAttr =
- (
- 'nsslapd-directory' => \&getNewDbDir,
- 'nsslapd-db-logdirectory' => \&getNewDbDir,
- 'nsslapd-changelogdir' => \&getNewDbDir,
- 'nsds5replicacredentials' => \&migrateCredentials,
- 'nsmultiplexorcredentials' => \&migrateCredentials,
- 'nsstate' => \&removensState,
- 'nsslapd-idl-switch' => \&migIdlSwitch
- );
- sub copyDatabaseDirs {
- my $srcdir = shift;
- my $destdir = shift;
- my $filesonly = shift;
- my @errs;
- my $isold = isOldDatabase($srcdir, \@errs);
- if (@errs) {
- return @errs;
- } elsif ($isold) {
- return ('error_database_too_old', $srcdir);
- }
- if (-d $srcdir && ! -d $destdir && !$filesonly) {
- debug(1, "Copying database directory $srcdir to $destdir\n");
- if (system ("cp -p -r $srcdir $destdir")) {
- return ('error_copying_dbdir', $srcdir, $destdir, $?);
- }
- } elsif (! -d $srcdir) {
- return ("error_dbsrcdir_not_exist", $srcdir);
- } else {
- debug(1, "The destination directory $destdir already exists, copying files/dirs individually\n");
- $! = 0;
- debug(1, "Removing any existing db files in $destdir\n");
- foreach my $file (glob("$destdir/*")) {
- next if (! -f $file);
- unlink($file);
- if ($!) {
- return ("error_removing_temp_db_files", $destdir, $!);
- }
- }
- foreach my $file (glob("$srcdir/*")) {
- if (-f $file) {
- debug(3, "Copying $file to $destdir\n");
- if (system ("cp -p $file $destdir")) {
- return ('error_copying_dbfile', $file, $destdir, $?);
- }
- } elsif (-d $file && !$filesonly) {
- debug(3, "Copying $file to $destdir\n");
- if (system ("cp -p -r $file $destdir")) {
- return ('error_copying_dbdir', $file, $destdir, $?);
- }
- }
- }
- }
- return ();
- }
- # older versions may use the old Netscape names e.g. Netscape Administration Server
- # we have to convert these to the new names e.g. @capbrand@ Administration Server
- sub migrateNetscapeRoot {
- my $ldiffile = shift;
- my ($fh, $tmpldiffile);
- # create a temp inf file for writing for other processes
- # never overwrite the user supplied inf file
- ($fh, $tmpldiffile) = tempfile("nsrootXXXXXX", UNLINK => 0,
- SUFFIX => ".ldif", OPEN => 1,
- DIR => File::Spec->tmpdir);
- if (!open( MYLDIF, "$ldiffile" )) {
- debug(1, "Error: Can't open $ldiffile: $!");
- return;
- }
- my $in = new Mozilla::LDAP::LDIF(*MYLDIF);
- while (my $ent = readOneEntry $in) {
- my $dn = $ent->getDN();
- next if (!$dn); # netscaperoot should not have the empty dn
- $dn =~ s/\bNetscape\b/@capbrand@/g;
- $ent->setDN($dn);
- foreach my $attr (keys %{$ent}) {
- my @vals = $ent->getValues($attr);
- map { s/\bNetscape\b/@capbrand@/g } @vals;
- $ent->setValues($attr, @vals);
- }
- Mozilla::LDAP::LDIF::put_LDIF($fh, 78, $ent);
- }
- close( MYLDIF );
- close( $fh );
- return $tmpldiffile;
- }
- sub fixIntegerIndexes {
- my $mig = shift;
- my $inst_dir = shift;
- my $newdbdir = shift;
- if (!$mig->{integerattrs}) {
- debug(1, "No integer syntax attributes, no indexes fixed\n");
- return ();
- }
- # look at each index file in the db dir
- # if it is on our list of integer attributes,
- # remove it and re-create it
- my $dbname = basename($newdbdir);
- for (glob("$newdbdir/*.db4")) {
- my $indexname = basename($_, '.db4');
- if ($mig->{integerattrs}->{lc $indexname}) {
- $mig->msg($INFO, 'fixing_integer_attr_index', $indexname, $newdbdir);
- debug(1, "Removing file $_\n");
- if (! unlink $_) {
- debug(1, "Error: could not remove file $_: $!\n");
- return ('error_removing_index_file', $_, $!);
- }
- my $cmd = "$inst_dir/db2index -n \"$dbname\" -t \"$indexname\"";
- debug(1, "Re-creating index file $_: $cmd\n");
- $? = 0; # clear error condition
- my $output = `$cmd 2>&1`;
- if ($?) {
- return ('error_recreating_index_file', $_, $output);
- }
- debug(1, $output);
- } else {
- debug(3, "Index $indexname is not for an integer syntax attribute - skipping\n");
- }
- }
- return ();
- }
- # 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;
- # the ldif2db command will be in nsslapd-instancedir
- my $cfgent = $dest->search("cn=config", "base", "(objectclass=*)");
- my $inst_dir = $cfgent->getValues('nsslapd-instancedir');
- # 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 $fname = $_;
- my $dbname = basename($fname, '.ldif');
- my $deleteflag = 0;
- if ($fname =~ /NetscapeRoot.ldif$/) {
- $fname = migrateNetscapeRoot($fname);
- if ($fname) {
- # make sure $fname is owned by the server user
- my $cfgent = $dest->search("cn=config", "base", "(objectclass=*)");
- my $user = $cfgent->getValues('nsslapd-localuser');
- my $uid = getpwnam $user;
- chown $uid, -1, $fname;
- $deleteflag = 1;
- } else {
- return ("error_creating_templdif", $!);
- }
- }
- my $cmd = "$inst_dir/ldif2db -n \"$dbname\" -i \"$fname\"";
- debug(1, "migrateDatabases: executing command $cmd\n");
- $? = 0; # clear error condition
- my $output = `$cmd 2>&1`;
- if ($deleteflag) {
- unlink($fname);
- }
- if ($?) {
- return ('error_importing_migrated_db', $fname, $?, $output);
- }
- debug(1, $output);
- $foundldif = 1;
- }
- if ($foundldif) {
- return (); # done - can do nothing else for cross-platform
- } elsif ($mig->{crossplatform}) { # cross platform requires LDIF files
- return ('ldif_required_for_cross_platform', "$mig->{oldsroot}/$inst/db");
- }
- # 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 = "";
- if ("@with_fhs_opt@") {
- $newdbdir = $newent->getValues('nsslapd-directory') ||
- "@localstatedir@/$mig->{pkgname}/$inst/db";
- } else {
- $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 = "";
- if ("@with_fhs_opt@") {
- $newdbdir = $newent->getValues('nsslapd-directory') ||
- "@localstatedir@/$mig->{pkgname}/$inst/db/$cn";
- } else {
- $newdbdir = $newent->getValues('nsslapd-directory') ||
- "@localstatedir@/lib/$mig->{pkgname}/$inst/db/$cn";
- }
- 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")) {
- return @errs;
- }
- # fix up the integer indexes
- if ($mig->{integerattrs}) {
- debug(3, "The schema has some integer attributes\n");
- if (@errs = fixIntegerIndexes($mig, $inst_dir, $newdbdir)) {
- return @errs;
- }
- } else {
- debug(3, "No integer attributes to fix for $newdbdir\n");
- }
- }
- }
- } while ($ent = $src->nextEntry());
- return ();
- }
- 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 $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');
- my @errs = copyDatabaseDirs($oldcldir, $newcldir);
- if (@errs) {
- return @errs;
- }
- }
- }
- return ();
- }
- sub fixAttrsInEntry {
- my ($ent, $mig, $inst) = @_;
- for my $attr (keys %{$ent}) {
- my $lcattr = lc $attr;
- if ($ignoreOld{$lcattr}) {
- debug(3, "fixAttrsInEntry: ignoring old invalid or obsolete attr $attr\n");
- $ent->remove($attr);
- next;
- } elsif ($transformAttr{$lcattr}) {
- my $newval = &{$transformAttr{$lcattr}}($ent, $attr, $mig, $inst);
- if (!$newval) {
- debug(2, "Removing attribute $attr from entry ", $ent->getDN(), "\n");
- $ent->remove($attr);
- } else {
- debug(2, "Setting new value $newval for attribute $attr in entry ", $ent->getDN(), "\n");
- $ent->setValues($attr, $newval);
- }
- } # else just keep as is
- }
- }
- sub mergeEntries {
- 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
- # if the attribute exists in the old entry but not the new one
- # we should probably add it (checking for special cases first)
- # if the attribute exists in the new entry but not the old one
- # we might have to delete it from the new entry
- # first, get a list of all attributes
- foreach my $attr (keys %{$old}) {
- if (! $new->exists($attr)) {
- $inoldonly{$attr} = $attr;
- } else {
- push @attrs, $attr;
- }
- }
- foreach my $attr (keys %{$new}) {
- if (! $old->exists($attr)) {
- $innewonly{$attr} = $attr;
- }
- }
-
- # iterate through the attr lists
- my $cn = lc $new->getValues("cn");
- foreach my $attr (keys %inoldonly, keys %innewonly, @attrs) {
- debug(3, "mergeEntries: merging entry ", $old->getDN(), " attr $attr\n");
- my $lcattr = lc $attr;
- if ($ignoreOld{$lcattr}) {
- debug(3, "mergeEntries: ignoring old invalid or obsolete attr $attr\n");
- next; # use new value or just omit if attr is obsolete
- } elsif ($transformAttr{$lcattr}) {
- # only transform if the value is in the old entry
- if (!$innewonly{$attr}) {
- my $oldval = $old->getValues($attr);
- my $newval = &{$transformAttr{$lcattr}}($old, $attr, $mig, $inst);
- if (!$newval) {
- debug(3, "Removing attribute $attr from entry ", $new->getDN(), "\n");
- $new->remove($attr);
- } else {
- debug(3, "Setting new value $newval for attribute $attr in entry ", $new->getDN(), "\n");
- $new->setValues($attr, $newval);
- }
- }
- } elsif ($cn eq "internationalization plugin" and $lcattr eq "nsslapd-pluginarg0") {
- debug(3, "mergeEntries: using new value of internationalization plugin nsslapd-pluginarg0\n");
- next; # use the new value of this path name
- } elsif ($cn eq "referential integrity postoperation" and $lcattr eq "nsslapd-pluginarg1") {
- debug(3, "mergeEntries: using new value of referential integrity postoperation nsslapd-pluginarg1\n");
- next; # use the new value of this path name
- } elsif ($innewonly{$attr}) {
- debug(3, "mergeEntries: removing attr $attr from new entry\n");
- $new->remove($attr); # in new but not old - just remove it
- } else {
- my $oldval = $old->getValues($attr);
- my $newval = $new->getValues($attr);
- $new->setValues($attr, $old->getValues($attr)); # use old value
- debug(3, "mergeEntries: using old val $oldval instead of new val $newval\n");
- }
- }
- }
- 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 @errs;
- if (@errs = getAllEntries($src, \%olddse, \@olddns)) {
- return @errs;
- }
- # 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
- if (@errs = getAllEntries($dest, \%newdse, \@allnewdns)) {
- return @errs;
- }
- for my $ndn (@allnewdns) {
- if (! exists $olddse{$ndn}) {
- push @newdns, $ndn;
- }
- }
- # 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
- # otherwise, merge the entries
- # @olddns contains the dns in the old dse.ldif, including ones that
- # may also be in the new dse.ldif
- # @newdns contains dns that are only in the new dse.ldif
- for my $dn (@olddns, @newdns) {
- my $oldent = $olddse{$dn};
- my $newent = $newdse{$dn};
- my $op;
- my $rc = 1;
- if ($mig->{crossplatform} && $noCrossPlatformDN{$dn}) {
- debug(1, "Cannot migrate the entry $dn - skipping\n");
- next;
- } elsif ($oldent && !$newent) {
- if (!$ignoreOldEntries{$dn}) { # make sure it's not obsolete
- # may have to fix up some values in the old entry
- fixAttrsInEntry($oldent, $mig, $inst);
- $rc = $dest->add($oldent);
- $op = "add";
- } else {
- debug(2, "Ignoring entry $dn - configuration not supported\n");
- }
- } elsif (!$oldent && $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, $mig, $inst);
- $rc = $dest->update($newent);
- $op = "update";
- }
-
- 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'
- );
- # these indexes are handled specially by the db code
- my %intattrstoskip = (
- 'numsubordinates' => 'numSubordinates',
- 'hassubordinates' => 'hasSubordinates'
- );
- sub fixup99user {
- my $mig = shift; # the Migration object
- my $inst = shift; # The name of the instance
- my $newschemadir = shift; # the new instance's schema path
- my %attrstoskip = ();
- my %objclassestoskip = ();
- my $uid;
- my $gid;
- my $mode;
- # Read every schema file in the legacy server's schema directory
- for (glob("$mig->{oldsroot}/$inst/config/schema/*.ldif")) {
- if (!open( OLDSCHEMA, $_ )) {
- debug(0, "Can't open schema file $_: $!\n");
- next;
- }
- # Read attributes from each file, looking for ones that contain
- # the string "DESC ''".
- my $in = new Mozilla::LDAP::LDIF(*OLDSCHEMA);
- while (my $ent = readOneEntry $in) {
- my @attrs = $ent->getValues('attributeTypes');
- my @objclasses = $ent->getValues('objectClasses');
- foreach my $attr (@attrs) {
- debug(4, "Checking if attribute should be added to skip list ($attr)\n");
- if ($attr =~ /\(\s*(\S*)\s*NAME .* DESC \'\'/) {
- # Store the OID of those in an associative array for
- # quick lookups later.
- debug(3, "Adding attribute to list to skip (OID $1)\n");
- $attrstoskip{"$1"} = 1;
- }
- }
- foreach my $objclass (@objclasses) {
- debug(4, "Checking if objectclass should be added to skip list ($objclass)\n");
- if ($objclass =~ /\(\s*(\S*)\s*NAME .* DESC \'\'/) {
- # Store the OID of those in an associative array for
- # quick lookups later.
- debug(3, "Adding objectclass to list to skip (OID $1)\n");
- $objclassestoskip{"$1"} = 1;
- }
- }
- }
- close(OLDSCHEMA);
- }
- # Open the 99user.ldif file in the new server schema directory, which is a
- # copy of the one in the legacy server. Also open a tempfile.
- if (!open(USERSCHEMA, "$newschemadir/99user.ldif")) {
- return ("error_opening_schema", "$newschemadir/99user.ldif", $!);
- }
- # Open a tempfile to write the cleaned 99user.ldif to
- if (!open(TMPSCHEMA, ">$newschemadir/99user.ldif.tmp")) {
- close(USERSCHEMA);
- return ("error_opening_schema", "$newschemadir/99user.ldif.tmp", $!);
- }
- # Iterate through every attribute in the 99user.ldif file and write them to the
- # tempfile if their OID doesn't exist in the "bad schema" array.
- my $in = new Mozilla::LDAP::LDIF(*USERSCHEMA);
- while (my $ent = readOneEntry $in) {
- my @attrs = $ent->getValues('attributeTypes');
- my @objclasses = $ent->getValues('objectClasses');
- my @keepattrs;
- my @keepobjclasses;
- foreach my $attr (@attrs) {
- if ($attr =~ /\(\s*(\S*)\s*NAME/) {
- debug(3, "Checking if attribute should be trimmed (OID $1)\n");
- # See if this OID is in our list of attrs to skip
- if ($attrstoskip{"$1"}) {
- debug(2, "Trimming attribute from 99user.ldif (OID $1)\n");
- next;
- }
- }
- # Keep this value
- debug(3, "Keeping attribute in 99user.ldif (OID $1)\n");
- push @keepattrs, $attr;
- }
- foreach my $objclass (@objclasses) {
- if ($objclass =~ /\(\s*(\S*)\s*NAME/) {
- debug(3, "Checking if objectclass should be trimmed (OID $1)\n");
- # See if this OID is in our list of objectclasses to skip
- if ($objclassestoskip{"$1"}) {
- debug(2, "Trimming objectclass from 99user.ldif (OID $1)\n");
- next;
- }
- }
- # Keep this value
- debug(3, "Keeping objectclass in 99user.ldif (OID $1)\n");
- push @keepobjclasses, $objclass;
- }
- # Update the entry with the values we want to keep
- if ($#keepattrs >= $[) {
- $ent->setValues("attributetypes", @keepattrs);
- } else {
- $ent->remove("attributetypes");
- }
- if ($#keepobjclasses >= $[) {
- $ent->setValues("objectclasses", @keepobjclasses);
- } else {
- $ent->remove("objectclasses");
- }
- # Write the entry to temp schema file
- my $oldfh = select(TMPSCHEMA);
- $ent->printLDIF();
- select($oldfh);
- }
- close(USERSCHEMA);
- close(TMPSCHEMA);
- # Make the ownership and permissions on the temp schema file
- # the same as the copied 99user.ldif.
- ($mode, $uid, $gid) = (stat("$newschemadir/99user.ldif"))[2,4,5];
- if ((chown $uid, $gid, "$newschemadir/99user.ldif.tmp") != 1) {
- return ("error_schema_permissions", "$newschemadir/99user.ldif.tmp", $!);
- }
- if ((chmod $mode, "$newschemadir/99user.ldif.tmp") != 1) {
- return ("error_schema_permissions", "$newschemadir/99user.ldif.tmp", $!);
- }
- # Replace the copied 99user.ldif with the trimmed file.
- if ((rename "$newschemadir/99user.ldif.tmp", "$newschemadir/99user.ldif") != 1) {
- return ("error_renaming_schema", "$newschemadir/99user.ldif.tmp", "$newschemadir/99user.ldif", $!);
- }
- return();
- }
- 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 @errs;
- 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", $_, $!);
- }
- }
- # fixup any attributes with missing descriptions in 99user.ldif
- if (@errs = fixup99user($mig, $inst, $newschemadir)) {
- return @errs;
- }
- if (!$mig->{crossplatform}) {
- # now, for all of the new schema, we need to get the list of attribute
- # types with INTEGER syntax, including derived types (e.g. SUP 'attr')
- # not required for cross platform because import of the old ldif file
- # will automatically recreate all indexes
- my %intattrs = ();
- for (glob("$newschemadir/*.ldif")) {
- # read in schema entry from LDIF
- if (!open( MYSCHEMA, $_ )) {
- debug(0, "Can't open schema file $_: $!\n");
- next;
- }
- my $in = new Mozilla::LDAP::LDIF(*MYSCHEMA);
- while (my $ent = readOneEntry $in) {
- my @attrs = $ent->getValues('attributeTypes');
- foreach my $attr (@attrs) {
- # first see if the attribute definition uses INTEGER syntax
- # else see if the super uses INTEGER - note this assumes the attributes
- # are listed in the files in SUP order - that is, an attribute does
- # not reference a SUP before it is defined
- if ($attr =~ / NAME (?:\(\s)?[\']?(\w+)[\']?.* SYNTAX 1.3.6.1.4.1.1466.115.121.1.27[\{\s]/) {
- next if ($intattrstoskip{lc $1});
- $intattrs{lc $1} = $1;
- } elsif (($attr =~ / NAME (?:\(\s)?[\']?(\w+)[\']?.*SUP [\']?(\w+)[\']?/) &&
- $intattrs{lc $2}) {
- next if ($intattrstoskip{lc $1});
- $intattrs{lc $1} = $1;
- }
- }
- }
- close MYSCHEMA;
- }
- # %intattrs now contains all of the integer valued attributes
- $mig->{integerattrs} = \%intattrs; # hashref
- }
- 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;
- my @errs;
- # migration needs to know the instance directory for the directory
- # servers - this assumes they are all in the same place
- if (!$mig->{ServerRoot}) {
- if ("@with_fhs_opt@") {
- $mig->{ServerRoot} = "$mig->{inf}->{General}->{prefix}/opt/@PACKAGE_NAME@";
- } else {
- $mig->{ServerRoot} = "$mig->{inf}->{General}->{prefix}@serverdir@";
- }
- }
- # for each instance
- foreach my $inst (@{$mig->{instances}}) {
- if (-f "$mig->{configdir}/$inst/dse.ldif") {
- $mig->msg($WARN, 'instance_already_exists', "$mig->{configdir}/$inst/dse.ldif");
- next;
- }
- # 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 $oldconfigdir = "$mig->{oldsroot}/$inst/config";
- my $inf = createInfFromConfig($oldconfigdir, $inst, \@errs);
- if (@errs) {
- $mig->msg(@errs);
- return 0;
- }
- if (!$inf) {
- $mig->msg($FATAL, 'error_opening_dseldif', "$oldconfigdir/dse.ldif", $!);
- return 0;
- }
- debug(2, "Using inffile $inf->{filename} created from $oldconfigdir\n");
- # create servers but do not start them until after databases
- # have been migrated
- $inf->{slapd}->{start_server} = 0;
- # create the new instance
- @errs = createDSInstance($inf);
- unlink($inf->{filename});
- if (@errs) {
- $mig->msg(@errs);
- $mig->msg($FATAL, 'error_creating_dsinstance', $inst);
- return 0;
- } else {
- $mig->msg('created_dsinstance', $inst);
- }
- my $src = new FileConn("$oldconfigdir/dse.ldif", 1); # read-only
- if (!$src) {
- $mig->msg($FATAL, 'error_opening_dseldif', "$oldconfigdir/dse.ldif", $!);
- return 0;
- }
- my $dest = new FileConn("$mig->{configdir}/$inst/dse.ldif");
- if (!$dest) {
- $src->close();
- $mig->msg($FATAL, 'error_opening_dseldif', "$mig->{configdir}/$inst/dse.ldif", $!);
- return 0;
- }
- @errs = migrateDSInstance($mig, $inst, $src, $dest);
- $src->close();
- $dest->close();
- if (@errs) {
- $mig->msg(@errs);
- return 0;
- }
- # ensure any selinux relabeling gets done if needed
- DSCreate::updateSelinuxPolicy($inf);
- # finally, start the server
- if ($mig->{start_servers}) {
- $inf->{slapd}->{start_server} = 1;
- if (@errs = DSCreate::startServer($inf)) {
- $mig->msg(@errs);
- return 0;
- }
- }
- }
- return 1;
- }
- #############################################################################
- # Mandatory TRUE return value.
- #
- 1;
- # emacs settings
- # Local Variables:
- # mode:perl
- # indent-tabs-mode: nil
- # tab-width: 4
- # End:
|