Author: rmeggins
Update of /cvs/dirsec/ldapserver/ldap/admin/src/scripts
In directory cvs1.fedora.phx.redhat.com:/tmp/cvs-serv1668/ldapserver/ldap/admin/src/scripts
Modified Files:
DSCreate.pm.in DSMigration.pm.in FileConn.pm Util.pm.in
remove-ds.pl.in setup-ds.res.in
Log Message:
Resolves: bug 468474
Bug Description: migration results in incomplete admin server sie
Reviewed by: nkinder (Thanks!)
Fix Description: This is a redesign of one of the core pieces of the setup/migration code - the code that adds the LDAP entries in various places. For starters, I removed the code that would implicitly delete existing trees. This is the root cause of this bug, and other similar problems with setup/instance creation that have been reported. We should never implicitly delete entries. Instead, we should explicitly delete entries by using the changetype: delete in an LDIF template file.
Another source of problems was that to update an entry, we would delete it and add it back. This caused some configuration settings to be wiped out (e.g. encryption settings). We cannot do this any more. The LDIF template entries have been modified to have two sets of information for each entry that requires update - the entry to add if no entry exists (the full entry) or the changes to make to the entry if it does exist. The code in Util.pm has been changed to ignore duplicate entries and to ignore changes made to entries that do not exist.
Another source of problems with migration is that the error checking was not adequate, especially with FileConn and dse.ldif reading. The fix is to add better error checking and reporting in these areas of code, including error messages.
Yet another problem is the run_dir handling. On many platforms the run_dir is shared among all DS instances and the admin server. Older versions of the software allowed you to run the servers as root. We have to make sure run_dir is usable by the least privileged user of all of the servers.
Platforms tested: RHEL4
Flag Day: no
Doc impact: no
Index: DSCreate.pm.in
===================================================================
RCS file: /cvs/dirsec/ldapserver/ldap/admin/src/scripts/DSCreate.pm.in,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -r1.15 -r1.16
--- DSCreate.pm.in 17 Dec 2008 17:22:22 -0000 1.15
+++ DSCreate.pm.in 24 Feb 2009 14:24:46 -0000 1.16
@@ -215,6 +215,28 @@
return @errs;
}
}
+ # run_dir is a special case because it is usually shared among
+ # all instances and the admin server
+ # all instances must be able to write to it
+ # if the SuiteSpotUserID is root or 0, we can just skip
+ # this because root will have access to it - we really
+ # shouldn't be using root anyway, primarily just for
+ # legacy migration support
+ # if there are two different user IDs that need access
+ # to this directory, then SuiteSpotGroup must be defined,
+ # and both users must be members of the SuiteSpotGroup
+ if (($inf->{General}->{SuiteSpotUserID} eq 'root') ||
+ (defined($inf->{General}->{SuiteSpotUserID}) &&
+ ($inf->{General}->{SuiteSpotUserID} =~ /^0$/))) {
+ # skip
+ debug(3, "Root user " . $inf->{General}->{SuiteSpotUserID} . " already has access to $inf->{slapd}->{run_dir} - skipping\n");
+ } else {
+ my $dir = $inf->{slapd}->{run_dir};
+ # rwx by user only, or by user & group if a group is defined
+ @errs = changeOwnerMode($inf, 7, $dir, 7);
+ debug(3, "Changed owner of $dir to " . $inf->{General}->{SuiteSpotUserID} . ": error @errs\n");
+ debug(3, "\t" . `/bin/ls -ld $dir`);
+ }
# set the group of the parent dir of config_dir and inst_dir
if (defined($inf->{General}->{SuiteSpotGroup})) {
for (qw(inst_dir config_dir)) {
@@ -372,7 +394,10 @@
}
}
- $conn->write($conffile);
+ if (!$conn->write($conffile)) {
+ $conn->close();
+ return ("error_writing_ldif", $conffile, $!);
+ }
$conn->close();
if (@errs = changeOwnerMode($inf, 6, $conffile)) {
@@ -506,11 +531,21 @@
my ($fh, $templdif) = tempfile("ldifXXXXXX", SUFFIX => ".ldif", OPEN => 0,
DIR => File::Spec->tmpdir);
+ if (!$templdif) {
+ return ('error_creating_templdif', $!);
+ }
my $conn = new FileConn;
$conn->setNamingContext($inf->{slapd}->{Suffix});
getMappedEntries($mapper, \@ldiffiles, \@errs, \&check_and_add_entry,
[$conn]);
- $conn->write($templdif);
+ if (@errs) {
+ $conn->close();
+ return @errs;
+ }
+ if (!$conn->write($templdif)) {
+ $conn->close();
+ return ('error_writing_ldif', $templdif, $!);
+ }
$conn->close();
if (@errs) {
return @errs;
Index: DSMigration.pm.in
===================================================================
RCS file: /cvs/dirsec/ldapserver/ldap/admin/src/scripts/DSMigration.pm.in,v
retrieving revision 1.26
retrieving revision 1.27
diff -u -r1.26 -r1.27
--- DSMigration.pm.in 17 Feb 2009 15:47:48 -0000 1.26
+++ DSMigration.pm.in 24 Feb 2009 14:24:47 -0000 1.27
@@ -938,11 +938,15 @@
# extract the information needed for ds_newinst.pl
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;
}
+ 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
@@ -960,7 +964,16 @@
}
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();
Index: FileConn.pm
===================================================================
RCS file: /cvs/dirsec/ldapserver/ldap/admin/src/scripts/FileConn.pm,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- FileConn.pm 14 Sep 2007 02:41:13 -0000 1.4
+++ FileConn.pm 24 Feb 2009 14:24:47 -0000 1.5
@@ -67,7 +67,9 @@
$self->setNamingContext($_);
}
$self->setNamingContext(""); # root DSE
- $self->read($filename);
+ if (!$self->read($filename)) {
+ return;
+ }
return $self;
}
@@ -90,10 +92,14 @@
}
if (!$self->{filename}) {
- return;
+ return 1; # no filename given - ok
+ }
+
+ if (!open( MYLDIF, "$filename" )) {
+ confess "Can't open $filename: $!";
+ return 0;
}
- open( MYLDIF, "$filename" ) || confess "Can't open $filename: $!";
my $in = new Mozilla::LDAP::LDIF(*MYLDIF);
$self->{reading} = 1;
while ($ent = readOneEntry $in) {
@@ -103,6 +109,8 @@
}
delete $self->{reading};
close( MYLDIF );
+
+ return 1;
}
sub setNamingContext {
@@ -175,16 +183,22 @@
}
if (!$self->{filename} or $self->{readonly} or $self->{reading}) {
- return;
+ return 1; # ok - no filename given - just ignore
+ }
+
+ if (!open( MYLDIF, ">$filename" )) {
+ confess "Can't write $filename: $!";
+ return 0;
}
- open( MYLDIF, ">$filename" ) || confess "Can't write $filename: $!";
$self->iterate("", LDAP_SCOPE_SUBTREE, \&writecb, \*MYLDIF);
for (keys %{$self->{namingContexts}}) {
next if (!$_); # skip "" - we already did that
$self->iterate($_, LDAP_SCOPE_SUBTREE, \&writecb, \*MYLDIF);
}
close( MYLDIF );
+
+ return 1;
}
sub setErrorCode {
@@ -372,8 +386,7 @@
if ($self->isNamingContext($ndn) and
!exists($self->{$ndn}->{data})) {
$self->{$ndn}->{data} = $entry;
- $self->write();
- return 1;
+ return $self->write();
}
if (exists($self->{$ndn})) {
@@ -415,9 +428,7 @@
# process omits the deleted attrs via the Entry FETCH, FIRSTKEY, and NEXTKEY
# methods
$self->{$ndn}->{data} = cloneEntry($entry);
- $self->write();
-
- return 1;
+ return $self->write();
}
sub delete {
@@ -464,8 +475,7 @@
# delete this node
delete $self->{$ndn};
- $self->write();
- return 1;
+ return $self->write();
}
1;
Index: Util.pm.in
===================================================================
RCS file: /cvs/dirsec/ldapserver/ldap/admin/src/scripts/Util.pm.in,v
retrieving revision 1.18
retrieving revision 1.19
diff -u -r1.18 -r1.19
--- Util.pm.in 17 Dec 2008 17:22:22 -0000 1.18
+++ Util.pm.in 24 Feb 2009 14:24:47 -0000 1.19
@@ -40,7 +40,7 @@
use Mozilla::LDAP::Conn;
use Mozilla::LDAP::Utils qw(normalizeDN);
-use Mozilla::LDAP::API; # Direct access to C API
+use Mozilla::LDAP::API qw(:constant ldap_explode_dn ldap_err2string) ; # Direct access to C API
use Mozilla::LDAP::LDIF;
require Exporter;
@@ -172,85 +172,6 @@
return 0;
}
-my %ignorelist = (
- "nsslapd-directory", "nsslapd-directory",
- "nsslapd-require-index", "nsslapd-require-index",
- "nsslapd-readonly", "nsslapd-readonly",
- "modifytimestamp", "modifyTimestamp",
- "createtimestamp", "createTimestamp",
- "installationtimestamp", "installationTimestamp",
- "creatorsname", "creatorsName",
- "modifiersname", "modifiersName",
- "numsubordinates", "numSubordinates"
-);
-
-my %speciallist = (
- "uniquemember", 1,
- "aci", 1
-);
-
-# compare 2 entries
-# return 0 if they match 100% (exception: %ignorelist).
-# return 1 if they match except %speciallist.
-# return -1 if they do not match.
-sub comp_entries
-{
- my ($e0, $e1) = @_;
- my $rc = 0;
- foreach my $akey ( keys %{$e0} )
- {
- next if ( $ignorelist{lc($akey)} );
- my $aval0 = $e0->{$akey};
- my $aval1 = $e1->{$akey};
- my $a0max = $#{$aval0};
- my $a1max = $#{$aval1};
- my $amin = $#{$aval0};
- if ( $a0max != $a1max )
- {
- if ( $speciallist{lc($akey)} )
- {
- $rc = 1;
- if ( $a0max < $a1max )
- {
- $amin = $a0max;
- }
- else
- {
- $amin = $a1max;
- }
- }
- else
- {
- $rc = -1;
- return $rc;
- }
- }
- my @sval0 = sort { $a cmp $b } @{$aval0};
- my @sval1 = sort { $a cmp $b } @{$aval1};
- for ( my $i = 0; $i <= $amin; $i++ )
- {
- my $isspecial = -1;
- if ( $sval0[$i] ne $sval1[$i] )
- {
- if ( 0 > $isspecial )
- {
- $isspecial = $speciallist{lc($akey)};
- }
- if ( $isspecial )
- {
- $rc = 1;
- }
- else
- {
- $rc = -1;
- return $rc;
- }
- }
- }
- }
- return $rc;
-}
-
# if the entry does not exist on the server, add the entry.
# otherwise, do nothing
# you can use this as the callback to getMappedEntries, so
@@ -272,9 +193,18 @@
my $sentry = $conn->search($aentry->{dn}, "base", "(objectclass=*)", 0, ("*", "aci"));
if ($sentry) {
debug(3, "check_and_add_entry: Found entry " . $sentry->getDN() . "\n");
+ if (! @ctypes) { # entry exists, and this is not a modify op
+ debug(3, "check_and_add_entry: skipping entry " . $sentry->getDN() . "\n");
+ return 1; # ignore - return success
+ }
} else {
debug(3, "check_and_add_entry: Entry not found " . $aentry->{dn} .
" error " . $conn->getErrorString() . "\n");
+ if (@ctypes) { # uh oh - attempt to del/mod an entry that doesn't exist
+ debug(3, "check_and_add_entry: attepting to @ctypes the entry " . $aentry->{dn} .
+ " that does not exist\n");
+ return 1; # ignore - return success
+ }
}
do
{
@@ -289,39 +219,7 @@
my $op = $OP_NONE;
if ( 0 > $#ctypes ) # aentry: complete entry
{
- $op = $OP_ADD;
-
- my $rc = -1;
- if ( $sentry && !$fresh )
- {
- $rc = comp_entries( $sentry, $aentry );
- }
- if ( 0 == $rc && !$fresh )
- {
- # the identical entry exists on the configuration DS.
- # no need to add the entry.
- $op = $OP_NONE;
- goto out;
- }
- elsif ( (1 == $rc) && !$fresh )
- {
- $op = $OP_MOD;
- @addtypes = keys %{$aentry}; # add all attrs
- }
- elsif ( $sentry && $sentry->{dn} )
- {
- # $fresh || $rc == -1
- # an entry having the same DN exists, but the attributes do not
- # match. remove the entry and the subtree underneath.
- debug(1, "Deleting an entry dn: $sentry->{dn} ...\n");
- $rc = delete_all($conn, $sentry);
- if ( 0 != $rc )
- {
- push @{$errs}, 'error_deleteall_entries', $sentry->{dn}, $conn->getErrorString();
- debug(1, "Error deleting $sentry->{dn}\n");
- return 0;
- }
- }
+ $op = $OP_ADD; # just add the entry
}
else # aentry: modify format
{
@@ -371,9 +269,13 @@
}
debug(1, "Entry $aentry->{dn} is deleted\n");
}
- elsif ( 0 < $op ) # $sentry exists
+ elsif ( 0 < $op ) # modify op
{
my $attr;
+ my @errsToIgnore;
+ if (@addtypes) {
+ push @errsToIgnore, LDAP_TYPE_OR_VALUE_EXISTS;
+ }
foreach $attr ( @addtypes )
{
foreach my $val ($aentry->getValues($attr))
@@ -388,6 +290,9 @@
debug(3, "Replacing attr=$attr values=" . $aentry->getValues($attr) . " to entry $aentry->{dn}\n");
$sentry->setValues($attr, @vals);
}
+ if (@deltypes) {
+ push @errsToIgnore, LDAP_NO_SUCH_ATTRIBUTE;
+ }
foreach $attr ( @deltypes )
{
# removeValue takes a single value only
@@ -410,11 +315,15 @@
if ( $rc != 0 )
{
my $string = $conn->getErrorString();
- push @{$errs}, 'error_updating_entry', $sentry->{dn}, $string;
debug(1, "ERROR: updating an entry $sentry->{dn} failed, error: $string\n");
- $aentry->printLDIF();
- $conn->close();
- return 0;
+ if (grep /^$rc$/, @errsToIgnore) {
+ debug(1, "Ignoring error $rc returned by adding @addtypes deleting @deltypes\n");
+ } else {
+ push @{$errs}, 'error_updating_entry', $sentry->{dn}, $string;
+ $aentry->printLDIF();
+ $conn->close();
+ return 0;
+ }
}
}
if ( $sentry )
@@ -793,19 +702,32 @@
my $fname = "$configdir/dse.ldif";
my $id;
($id = $inst) =~ s/^slapd-//;
- if (! -f $fname) {
+ if (! -f $fname || ! -r $fname) {
push @{$errs}, "error_opening_dseldif", $fname, $!;
return 0;
}
my $conn = new FileConn($fname, 1);
+ if (!$conn) {
+ push @{$errs}, "error_opening_dseldif", $fname, $!;
+ return 0;
+ }
my $ent = $conn->search("cn=config", "base", "(objectclass=*)");
if (!$ent) {
push @{$errs}, "error_opening_dseldif", $fname, $!;
+ $conn->close();
return 0;
}
my ($outfh, $inffile) = tempfile(SUFFIX => '.inf');
+ if (!$outfh || !$inffile) {
+ push @{$errs}, "error_opening_tempinf", $fname, $!;
+ if ($outfh) {
+ close $outfh;
+ }
+ $conn->close();
+ return 0;
+ }
print $outfh "[General]\n";
print $outfh "FullMachineName = ", $ent->getValues('nsslapd-localhost'), "\n";
print $outfh "SuiteSpotUserID = ", $ent->getValues('nsslapd-localuser'), "\n";
Index: remove-ds.pl.in
===================================================================
RCS file: /cvs/dirsec/ldapserver/ldap/admin/src/scripts/remove-ds.pl.in,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- remove-ds.pl.in 13 Feb 2009 20:05:59 -0000 1.1
+++ remove-ds.pl.in 24 Feb 2009 14:24:47 -0000 1.2
@@ -162,6 +162,10 @@
# read the config file to find out the paths
my $dseldif = "@instconfigdir@/$instname/dse.ldif";
my $conn = new FileConn($dseldif);
+if (!$conn) {
+ print STDERR "Error: Could not open config file $dseldif: Error $!\n";
+ exit 1;
+}
my $dn = "cn=config";
my $entry = $conn->search($dn, "base", "(cn=*)", 0);
Index: setup-ds.res.in
===================================================================
RCS file: /cvs/dirsec/ldapserver/ldap/admin/src/scripts/setup-ds.res.in,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- setup-ds.res.in 17 Dec 2008 18:58:21 -0000 1.14
+++ setup-ds.res.in 24 Feb 2009 14:24:47 -0000 1.15
@@ -127,3 +127,7 @@
or use a different ServerIdentifier to create another instance.\n
error_opening_init_ldif = Could not open the initial LDIF file '%s'.\
The file was not found or could not be read.\n
+error_opening_dseldif = Could not open the DSE config file '%s'. Error: %s\n
+error_opening_tempinf = Could not create temporary .inf file for config. Error: %s\n
+error_writing_ldif = Could not write the LDIF file '%s'. Error: %s\n
+error_creating_templdif = Could not create temporary LDIF file. Error: %s\n