Skip to content
Permalink
6bb982f099
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
executable file 550 lines (439 sloc) 13.6 KB
#!/usr/bin/perl
#
# Copyright (c) 2014-2015 Evolveum
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Author: Radovan Semancik
#
# Required packages:
# Ubuntu: libnet-ldap-perl libauthen-sasl-perl libuuid-perl
use strict;
use warnings;
use Net::LDAP::LDIF;
use Date::Format;
use UUID;
use Getopt::Long qw(:config bundling no_auto_abbrev pass_through);
use Pod::Usage;
my @userDbTypes = qw(hdb bdb mdb);
my ($verbose,$optHelp,$optYes,$optRc,$optDoNothing);
my ($configDir,$dbDir,$serviceName,$ldapUser,$ldapGroup);
my $debug = 0;
$SIG{__DIE__} = sub { Carp::confess(@_) };
my $command;
if ($ARGV[0] !~ /^-/) {
$command = shift;
}
GetOptions (
"rc" => \$optRc,
"do-nothing|n" => \$optDoNothing,
"yes|y" => \$optYes,
"verbose|v" => \$verbose,
"debug|d" => \$debug,
"help|h" => \$optHelp,
) or usage();
usage() if $optHelp;
$configDir = "/etc/ldap" unless $configDir;
$dbDir = "/var/lib/ldap" unless $dbDir;
$serviceName = "slapd" unless $serviceName;
$ldapUser = "openldap" unless $ldapUser;
$ldapGroup = "openldap" unless $ldapGroup;
my $cnConfigDir;
if (!defined($command)) {
$command = shift;
}
if (!$command) { usage(); }
if ($command eq "--help") { usage(); }
if ($command eq "list-suffixes") { listSuffixes() }
elsif ($command eq "delete-suffix") { deleteSuffix() }
elsif ($command eq "create-suffix") { createSuffix() }
elsif ($command eq "delete-schema") { deleteSchema() }
elsif ($command eq "delete-all") { deleteAll() }
elsif ($command eq "help") { usage() }
else { usage() }
sub listSuffixes {
checkConfigDirs();
# TODO
}
sub createSuffix {
my $suffix;
if ($ARGV[0] !~ /^-/) {
$suffix = shift @ARGV;
}
my ($dbDirectory,$rootDn,$rootPassword,$dbType);
GetOptions (
"dbDir|B=s" => \$dbDirectory,
"rootDn=s" => \$rootDn,
"rootPassword=s" => \$rootPassword,
) or usage();
if (!defined($suffix)) {
$suffix = shift @ARGV;
}
if (!$suffix) {
die("No suffix name specified\n");
}
$dbDirectory = "/var/lib/ldap/$suffix" unless $dbDirectory;
$rootDn = "cn=admin,".$suffix unless $rootDn;
$rootPassword = "secret" unless $rootPassword;
$dbType = "hdb" unless $dbType;
checkConfigDirs();
stopSlapd();
my $entry = findConfigObjectBySuffix($suffix);
if ($entry) {
die("Suffix $suffix already exists\n");
}
my $index = findLastDbConfigFileIndex();
my $olcDatabaseName = "{".($index+1)."}$dbType";
my $olcDatabaseRdn = "olcDatabase=$olcDatabaseName";
my $dbConfigPath = "$cnConfigDir/$olcDatabaseRdn.ldif";
# print("Max: $index, file: $dbConfigPath\n");
my $nowTimestamp = time2str("%Y%m%d%k%M%SZ",time());
my ($uuid,$uuidStr);
UUID::generate($uuid);
UUID::unparse($uuid,$uuidStr);
$entry = Net::LDAP::Entry->new($olcDatabaseRdn,
objectClass => [ 'olcDatabaseConfig', 'olcHdbConfig' ],
olcDatabase => $olcDatabaseName,
olcDbDirectory => $dbDirectory,
olcSuffix => $suffix,
olcRootDN => $rootDn,
olcRootPW => $rootPassword,
olcDbCheckpoint => '512 30',
olcDbConfig => [
'{0}set_cachesize 0 2097152 0',
'{1}set_lk_max_objects 1500',
'{2}set_lk_max_locks 1500',
'{3}set_lk_max_lockers 1500' ],
structuralObjectClass => 'olcHdbConfig',
entryUUID => $uuidStr,
creatorsName => 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth',
createTimestamp => $nowTimestamp,
olcAccess => [
'{0}to attrs=userPassword,shadowLastChange by dn="'.$rootDn.'" write by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth write by anonymous auth by self write by * none',
'{1}to dn.base="" by * read',
'{2}to * by dn="'.$rootDn.'" write by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth write by anonymous auth by self write by * none',
],
olcDbIndex => [ 'objectClass eq' ],
);
my $entryLdif = $entry->ldif;
$entryLdif =~ s/^\n*//m;
if ($optDoNothing) {
print("Would create file $dbConfigPath with the following content:\n");
print($entryLdif."<<\n");
} else {
open(LDIF,">$dbConfigPath");
print LDIF $entryLdif;
close(LDIF);
chownName($ldapUser, $ldapGroup, $dbConfigPath);
}
if (! -e $dbDirectory) {
if ($optDoNothing) {
print("Would create directory $dbDirectory\n");
} else {
mkdir($dbDirectory);
chownName($ldapUser, $ldapGroup, $dbDirectory);
}
}
startSlapd();
}
sub chownName {
my ($user,$group,@files) = @_;
my ($ulogin,$upass,$uid,$ugid) = getpwnam($user);
my ($glogin,$gpass,$gid) = getgrnam($group);
chown($uid,$gid,@files);
}
sub findLastDbConfigFileIndex {
my (@cnConfigNodes) = listDir($cnConfigDir);
my @dbNodes = grep { /^olcDatabase=/ } @cnConfigNodes;
my $max = 0;
foreach my $dbNode (@dbNodes) {
if (my ($num,$name) = ($dbNode =~ /^olcDatabase=\{(-?\d+)\}([^\s]+)\.ldif$/)) {
if (-f "$cnConfigDir/$dbNode") {
if ($num > $max) {
$max = $num;
}
} else {
die("What the heck is $cnConfigDir/$dbNode?\n");
}
}
}
return $max;
}
sub findConfigObjectBySuffix {
my ($suffix) = @_;
my (@cnConfigNodes) = listDir($cnConfigDir);
my @dbNodes = grep { /^olcDatabase=/ } @cnConfigNodes;
OUTER: foreach my $dbNode (@dbNodes) {
if (my ($num,$name) = ($dbNode =~ /^olcDatabase=\{(-?\d+)\}([^\s]+)\.ldif$/)) {
if (-f "$cnConfigDir/$dbNode") {
my $file="$cnConfigDir/$dbNode";
print "Processing file $file\n" if $verbose;
my $ldif = Net::LDAP::LDIF->new($file,"r");
while( not $ldif->eof()) {
my $entry = $ldif->read_entry();
if ($ldif->error()) {
die("LDIF error in $file:".$ldif->error_lines().": ".$ldif->error()."\n");
}
my $olcSuffix = $entry->get_value('olcSuffix');
if (!defined($olcSuffix) || $suffix ne $olcSuffix) {
print("Skipping DB $name because suffix does not match\n") if $verbose;
$ldif->done();
next OUTER;
}
$ldif->done();
return $entry;
}
$ldif->done();
} else {
die("What the heck is $cnConfigDir/$dbNode?\n");
}
}
}
return undef;
}
sub deleteSuffix {
my $suffix = shift @ARGV;
if (!$suffix) {
die("No suffix name specified\n");
}
deleteDbAndConfigFiles($suffix);
}
sub deleteAll {
deleteDbAndConfigFiles();
}
sub deleteDbAndConfigFiles {
my ($suffix) = @_;
checkConfigDirs();
# if (! -d $dbDir) {
# die("$dbDir is not a directory\n");
# }
stopSlapd();
#print("C: $cnConfigDir\n");
my (@cnConfigNodes) = listDir($cnConfigDir);
#print("N: ".join(", ",@cnConfigNodes)."\n");
my @dbNodes = grep { /^olcDatabase=/ } @cnConfigNodes;
#print("D: ".join(", ",@dbNodes)."\n");
my @deletedNodeNames;
OUTER: foreach my $dbNode (@dbNodes) {
if (my ($num,$name) = ($dbNode =~ /^olcDatabase=\{(-?\d+)\}([^\s\.]+)(\.ldif)?$/)) {
#print ("$num - $name\n");
if ($num < 1) {
print("Skipping DB $name because num is $num\n") if $verbose;
next;
}
if (!(grep{$_ eq $name} @userDbTypes)) {
print("Skipping DB $name because it is not know to be user DB type\n") if $verbose;
next;
}
if (-d "$cnConfigDir/$dbNode") {
# skip dirs for now. We will deal with them later
} elsif (-f "$cnConfigDir/$dbNode") {
my $file="$cnConfigDir/$dbNode";
print "Processing file $file\n" if $verbose;
my $ldif = Net::LDAP::LDIF->new($file,"r");
while( not $ldif->eof()) {
my $entry = $ldif->read_entry();
if ($ldif->error()) {
die("LDIF error in $file:".$ldif->error_lines().": ".$ldif->error()."\n");
}
my $olcSuffix = $entry->get_value('olcSuffix');
if ($suffix && ($suffix ne $olcSuffix)) {
print("Skipping DB $name because suffix does not match ($olcSuffix vs $suffix)\n") if $verbose;
$ldif->done();
next OUTER;
}
my $dbdir = $entry->get_value('olcDbDirectory');
print("Cleaning up DB directory $dbdir\n") if $verbose;
cleanupDbDir($dbdir);
}
$ldif->done();
deleteFile($file);
push @deletedNodeNames,"olcDatabase={$num}$name";
} else {
die("What the heck is $cnConfigDir/$dbNode?\n");
}
}
}
foreach my $dbNodeName (@deletedNodeNames) {
if (-d "$cnConfigDir/$dbNodeName") {
deleteDir("$cnConfigDir/$dbNodeName");
}
}
startSlapd();
if ($suffix && !@deletedNodeNames) {
die("Suffix $suffix not found\n");
}
}
sub deleteSchema {
my $schemaName = shift @ARGV;
if (!$schemaName) {
die("No schema name specified\n");
}
checkConfigDirs();
stopSlapd();
my $schemaIndex = undef;
($schemaName, $schemaIndex) = parseIndexedName($schemaName);
print "schemaName=$schemaName, schemaIndex=$schemaIndex\n" if $debug;
my $file = undef;
my (@schemaNodes) = listDir($cnConfigDir."/cn=schema");
foreach my $schemaNode (@schemaNodes) {
if (my ($entryIndex, $entryName) = ($schemaNode =~ /^cn=\{(-?\d+)\}([^\s\.]+)(\.ldif)?$/)) {
if ($entryName eq $schemaName) {
if (defined $schemaIndex && $schemaIndex != $entryIndex) {
die("Schema $schemaName present, but it does have index $entryIndex and not $schemaIndex\n");
}
$file="$cnConfigDir/cn=schema/$schemaNode";
last;
}
}
}
if (!$file) {
die("Schema $schemaName does not exist\n");
}
print "Deleting $file\n" if $debug;
deleteFile($file);
startSlapd();
}
### RC
sub stopSlapd {
if ($optRc) {
system("service $serviceName stop") == 0 or die("Service stop failed: $! ($?)\n");
} else {
my $out = `service $serviceName status`;
chomp($out);
if ($out !~ /is not running/i) {
die("slapd seems to be running ($out)\n");
}
}
}
sub startSlapd {
if ($optRc) {
system("service $serviceName start") == 0 or die("Service start failed: $! ($?)\n");
}
}
### Util
sub parseIndexedName {
my ($val) = @_;
if ($val =~ /^{(\d+)}/) {
return ($', $1);
} else {
return ($val, undef);
}
}
sub checkConfigDirs {
if (! -d $configDir) {
die("$configDir is not a directory\n");
}
my $slapdDDir = "$configDir/slapd.d";
if (! -d $slapdDDir) {
die("$configDir does not contain slapd.d subdirectory\n");
}
$cnConfigDir = "$slapdDDir/cn=config";
if (! -d $cnConfigDir) {
die("$configDir does not contain slapd.d/cn=config subdirectory\n");
}
}
sub deleteDir {
my ($dirpath) = @_;
my @nodes = listDir($dirpath);
foreach my $node (@nodes) {
my $nodepath = "$dirpath/$node";
if (-f $nodepath) {
deleteFile($nodepath);
} else {
die("Unexpected thing $nodepath, aborting\n");
}
}
if ($optDoNothing) {
print "Would delete directory $dirpath\n";
} else {
print "Deleting directory $dirpath\n" if $verbose;
rmdir($dirpath) or die("Error deleting directory $dirpath: $!\n");
}
}
sub cleanupDbDir {
my ($dirpath) = @_;
my @nodes = listDir($dirpath);
foreach my $node (@nodes) {
my $nodepath = "$dirpath/$node";
if (-f $nodepath) {
deleteFile($nodepath);
} else {
die("Unexpected thing $nodepath, aborting\n");
}
}
# do NOT delete the directory. we need it.
}
sub deleteFile {
my ($path) = @_;
if ($optDoNothing) {
print "Would delete file $path\n";
} else {
print "Deleting file $path\n" if $verbose;
unlink($path) or die("Error deleting file $path: $!\n");
}
}
sub listDir {
my ($dirpath) = @_;
opendir(my $dh, $dirpath) || die "can't opendir $dirpath: $!";
my @nodes = grep { /^[^\.]/ && "$dirpath/$_" } readdir($dh);
closedir $dh;
return @nodes;
}
### USAGE and DOCUMENTATION
sub usage {
pod2usage(-verbose => 2);
exit(1);
}
__END__
=head1 NAME
slapdadm - a command-line tool to configure stopped OpenLDAP instance.
=head1 SYNOPSIS
slapdadm [global options] command [command options]
=head1 OPTIONS
=head2 COMMANDS
=over 8
=item B<list-suffixes>
List directory suffixes configured on the server.
=item B<create-suffix> I<suffix>
Creates new directory suffix with an associated database.
It will create empty database without any entry - even without a root entry. This needs to be populated.
But it creates a default configuration for the suffix including root user and default ACLs.
=item B<delete-suffix> I<suffix>
Deletes an existing directory suffix together with an associated database and the data.
=item B<delete-all>
Deletes all suffixes and databases. This comes handy if your OpenLDAP comes pre-configured from your
distribution and you want it to be configured differently.
=item B<help>
Displays command usage summary.
=back
=head2 GLOBAL OPTIONS
=over 8
=item [ B<-v> | B<--verbose> ]
Increases verbosity.
=item B<--help>
Displays help message.
=back
=head1 DESCRIPTION
This command-line tool is used to configure a stopped OpenLDAP instance. The configuration
is done by direct manipulation of files in C</etc/ldap/slapd.d> directory and the database
files.
=head1 EXAMPLES
slapdadm delete-suffix dc=example,dc=com
=head1 NOTES
This is still work in progress. Please feel free to contribute.
=head1 AUTHOR
Radovan Semancik
=head1 SEE ALSO
C<slapdconf>
=cut