diff --git a/bin/ldap-schema-manager b/bin/ldap-schema-manager new file mode 100755 index 0000000000000000000000000000000000000000..aa90126911218f9ef5e4a38723f3314d436bb765 --- /dev/null +++ b/bin/ldap-schema-manager @@ -0,0 +1,444 @@ +#!/usr/bin/perl + +######################################################################## +# +# ldap-schema-manager +# +# Manipulate, insert and update schemas into the ldap server +# +# This code is part of FusionDirectory (http://www.fusiondirectory.org/) +# Copyright (C) 2011-2017 FusionDirectory +# +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. +# +######################################################################## + +use strict; +use warnings; + +use 5.008; + +my $ldap_host_options = '-Y EXTERNAL -H ldapi:///'; +my $path = "/etc/ldap/schema/"; +my $full_cmd = ""; +my $schema2ldif = "schema2ldif"; + +my $listschemas = 0; +my $modify = 0; +my $schemalist = 0; +my $yes_flag = 0; +my $pathunset = 1; +my $continue = 0; +my @schemas = (); +my @gen_files = (); +my $delete_ldif = 1; +foreach my $arg ( @ARGV ) { + if (not defined $ldap_host_options) { + $ldap_host_options = $arg; + } elsif ((lc($arg) eq '-i') || (lc($arg) eq '--insert')) { + if ($schemalist) { + usage(); + } + $schemalist = 1; + } elsif ((lc($arg) eq '-l') || (lc($arg) eq '--list')) { + $listschemas = 1; + } elsif ((lc($arg) eq '-c') || (lc($arg) eq '--continue')) { + $continue = 1; + } elsif ((lc($arg) eq '-e') || (lc($arg) eq '--empty')) { + if ($schemalist) { + usage(); + } + $modify = 2; + $schemalist = 1; + } elsif ((lc($arg) eq '-m') || (lc($arg) eq '--modify')) { + if ($schemalist) { + usage(); + } + $modify = 1; + $schemalist = 1; + } elsif ((lc($arg) eq '-o') || (lc($arg) eq '--options')) { + undef $ldap_host_options; + } elsif ((lc($arg) eq '-n') || (lc($arg) eq '--nodelete')) { + $delete_ldif = 0; + } elsif ((lc($arg) eq '-y') || (lc($arg) eq '--yes')) { + $yes_flag = 1; + } elsif ((lc($arg) eq '-h') || (lc($arg) eq '--help')) { + usage(); + } elsif ($schemalist) { + if ($arg !~ m|/|) { + # add path prefix if not a path + $arg = $path.$arg; + } + if ($arg =~ /^(.*)\.ldif$/) { + # ignore ".ldif" if it is there + push @schemas, $1; + } elsif ($arg =~ /^(.*)\.schema$/) { + if (system("$schema2ldif $arg > $1.ldif") == 0) { + push @schemas, $1; + push @gen_files, $1; + } else { + push @gen_files, $1; + die_with_error("Something went wrong while trying to convert $arg to ldif\n"); + } + } else { + push @schemas, $arg; + } + } elsif ($pathunset) { + $path = $arg; + if ($path !~ m|/$|) { + # add path prefix if not a path + $path = $path."/"; + } + $pathunset = 0; + } else { + usage(); + } +} + +# if --options is used with no value +usage () if (not defined $ldap_host_options); + +# die if user is not "root" +die_with_error ("! You have to run this script as root\n") if ($<!=0); + +my $add_cmd = "ldapadd $ldap_host_options -f "; +my $mod_cmd = "ldapmodify $ldap_host_options -f "; +my $ldapsearch = "ldapsearch $ldap_host_options "; +my $search_cmd = $ldapsearch." -b \"cn=schema,cn=config\" cn={*}"; +my $list_cmd = $search_cmd."* cn 2>/dev/null"; +my $cnconfig_cmd = $ldapsearch." -b \"cn=config\" cn=config dn 2>/dev/null | grep dn:"; + +if ($listschemas) { + list_schemas(); + exit 0; +} + +# die if the path doesn't exists +die_with_error ("! $path doesn't seems to exists\n") if (!-e $path); + +#die if we are not in cn=config +my $cnconfig = `$cnconfig_cmd`; +if (!($cnconfig =~ m/^dn:\s*cn=config$/)) { + die_with_error ("! This tool is only intended to be with with a cn=config backend, cn=config could not be found in the LDAP"); +} + +if (scalar(@schemas) == 0) { + usage("Missing schema list\n"); +} + +if ($modify == 2) { + unless (ask_yn_question("Are you sure you want to empty schema(s) ".join(", ",@schemas)."?")) + { + die_with_error("Aborting…\n"); + } +} + +$continue++; # activating continue feature only for insertions + +foreach my $schema (@schemas) { + my $schema_name = ""; + my $ldif_file; + + if ($modify < 2) { + # Searching schema name in ldif file first line. + open $ldif_file, q{<}, $schema.".ldif" or die "Could not open $schema.ldif file : $!\n"; + my $dn = ""; + while ($dn eq "") { + chomp($dn = <$ldif_file>); + } + if ($dn =~ /^dn: cn=([^,]+),/) { + $schema_name = $1; + } + close($ldif_file); + } + + # Fallback on file name + if ($schema_name eq "") { + $schema_name = $schema; + $schema_name =~ s|^.*/||; + } + + insert_schema($schema, $schema_name); +} + +remove_ldifs(); + +sub insert_schema +{ + my($schema, $schema_name) = @_; + my $schema_file; + my $update_file; + my $empty_file; + + $full_cmd = $search_cmd.$schema_name." cn"; + print ("\n"); + my $search = `$full_cmd`; + + if ($search !~ /# numEntries: 1/m) { + if ($modify) { + print "$schema_name does not exists in the LDAP, skipping…\n"; + } else { + # if the schema doesn't already exists in the LDAP server, adding it + $full_cmd = $add_cmd.$schema.".ldif"; + print "executing '$full_cmd'\n"; + if (system ($full_cmd) != 0) { + die_with_error ("Insertion failed!\n"); + } + } + } else { + if ($modify) { + if ($search !~ m/dn: ([^,]+),cn=schema,cn=config/) { + print "Could not parse existing dn for $schema_name, skipping…\n"; + return; + } + my $dn_part = $1; + # if the schema already exists in the LDAP server, modify it + if ($modify == 1) { + open($schema_file, q{<}, $schema.".ldif") or die_with_error('Could not open '."<".$schema.".ldif: $!"); + open($update_file, q{>}, $schema."_update.ldif") or die_with_error('Could not open '.">".$schema."_update.ldif: $!"); + push @gen_files, $schema."_update"; + my $attrs = 0; + my $classes = 0; + while (<$schema_file>) { + next if m/^#/; # remove comments + chomp; + next if m/^$/; # remove empty lines + if (m/^dn: cn=([^,]+),cn=schema,cn=config$/) { + print $update_file "dn: $dn_part,cn=schema,cn=config\n"; + print $update_file "changetype: modify\n"; + next; + } + if (!m/^olcAttributeTypes:/ && !m/^olcObjectClasses:/ && !m/^ /) { + #skip cn, objectClass, … + next; + } + + if (!$attrs && $classes) { + die "Malformed schema\n"; + } + + if (!$attrs && m/^olcAttributeTypes:/) { + $attrs = 1; + print $update_file "replace: olcAttributeTypes\n"; + } + if (!$classes && m/^olcObjectClasses:/) { + $classes = 1; + print $update_file "-\n"; + print $update_file "replace: olcObjectClasses\n"; + } + + print $update_file $_; + print $update_file "\n"; + } + close $schema_file; + close $update_file; + } else { # Emptying schema + open($empty_file, q{>}, $schema."_update.ldif") or die_with_error('Could not open '.">".$schema."_update.ldif: $!"); + push @gen_files, $schema."_update"; + print $empty_file "dn: $dn_part,cn=schema,cn=config\n"; + print $empty_file "changetype: modify\n"; + print $empty_file "delete: olcAttributeTypes\n"; + print $empty_file "-\n"; + print $empty_file "delete: olcObjectClasses\n"; + print $empty_file "-\n"; + close $empty_file; + } + $full_cmd = $mod_cmd.$schema."_update.ldif"; + print "executing '$full_cmd'\n"; + if (system ($full_cmd) != 0) { + die_with_error ("Insertion failed!\n"); + } + } else { + print "$schema_name already exists in the LDAP, skipping…\n"; + } + } +} + +sub remove_ldifs +{ + if ($delete_ldif) { + foreach my $file (@gen_files) { + unlink "$file.ldif" or print "Could not delete $file.ldif\n"; + } + } +} + +sub die_with_error +{ + my ($error) = @_; + if ($continue == 2) { + print "Error: $error\nContinuing…\n"; + } else { + remove_ldifs(); + die $error; + } +} + +sub list_schemas +{ + my @schemas = `$list_cmd`; + foreach my $schema (@schemas) { + if ($schema =~ m/cn:\s*{[0-9]+}(.*)$/) { + print "$1\n"; + } + } +} + +# ask a question send as parameter, and return true if the answer is "yes" +sub ask_yn_question { + return 1 if ($yes_flag); + my ($question) = @_; + print ( "$question [Yes/No]?\n" ); + + while ( my $input = <STDIN> ) { + # remove the \n at the end of $input + chomp $input; + + # if user's answer is "yes" + if ( lc($input) eq "yes" || lc($input) eq "y") { + return 1; + # else if he answer "no" + } elsif ( lc($input) eq "no" || lc($input) eq "n") { + return 0; + } + } +} + +sub usage +{ + (@_) && print STDERR "\n@_\n\n"; + + print STDERR << "EOF"; + usage: $0 [-y] [-n] [-c] [-o options] [path] [-h|-l|-i schema1 schema2|-m schema1 schema2|-e schema1 schema2] + + -h, --help : this (help) message + path : where to find the schemas + -i, --insert : specify the schemas to insert + -l, --list : list inserted schemas + -m, --modify : modify exising inserted schemas + -e, --empty : empty exising inserted schemas (do not remove them) + -n, --nodelete : do not delete generated ldifs at the end + -o, --options : set ldap options used (default is -Y EXTERNAL -H ldapi:///) + -c, --continue : continue on error(s) + -y, --yes : answer yes to all questions + +EOF + exit -1; +} + +exit 0; + +=head1 NAME + +ldap-schema-manager - insert schema needed by FusionDirectory into the ldap server + +=head1 SYNOPSIS + +ldap-schema-manager [-y] [-n] [-c] [-o options] [path] [-h|-l|-i schema1 schema2|-m schema1 schema2|-e schema1 schema2] + +=head1 DESCRIPTION + +This program will list, insert, empty or modify the ldap schemas into the ldap server. +If a schema is not listed as a path, it will be searched for in the provided path, or in /etc/ldap/schema/ if no path has been provided. To insert schemas from working directory prepend them with "./". +Schema with no extension is assumed to be .ldif. Specify .schema if you want the tool to autoconvert the schema to ldif file. See the examples for more information + +=head2 Options + +=over 6 + +=item -i + +This option insert the given list of schemas + +=item -m + +This option insert the given list of schemas, replacing already inserted versions of those schemas + +=item -e + +This option empty the given list of schemas, removing attributes and objectClasses from those. +This is useful because you cannot delete a schema without restarting slapd. + +=item -l + +This option list inserted schemas + +=item -n + +This option will make generated ldifs file to not be deleted after execution. Might be useful to understand errors. + +=item -c + +This option make the program continue even if an error occur + +=item -y + +This option answer yes to all questions. The only question right now is the confirmation one when you ask to empty a schema. + +=item -o + +This option allow you to specify specifics options to give to ldap commands such as ldapmodify, +but beware that you will not be able to see things like password prompts as the output of these commands is piped. + +=back + +=head1 EXAMPLES + + admin@ldapserver$ ldap-schema-manager -i /etc/ldap/otherschema/myschema.ldif + Insert the schema /etc/ldap/otherschema/myschema.ldif + + admin@ldapserver$ ldap-schema-manager -i /etc/ldap/otherschema/myschema.schema + Convert /etc/ldap/otherschema/myschema.schema to ldif and insert it + + admin@ldapserver$ ldap-schema-manager -i myschema + Insert the schema myschema.ldif from default directory (/etc/ldap/schema/) + + admin@ldapserver$ ldap-schema-manager -i myschema.schema + Insert the schema myschema.schema from default directory (/etc/ldap/schema/) + + admin@ldapserver$ ldap-schema-manager -m /etc/ldap/otherschema/myschema.schema + Convert /etc/ldap/otherschema/myschema.schema to ldif and replace the existing schema by this one + + admin@ldapserver$ ldap-schema-manager -e myschema + Empty the schema myschema + + admin@ldapserver$ ldap-schema-manager -o "-H ldap://my.ldap.com -ZZ -D 'cn=admin,cn=config' -w password -x" -l + Connect to another ldap server and list schemas + +=head1 BUGS + +Please report any bugs, or post any suggestions, to the fusiondirectory mailing list fusiondirectory-users or to +<https://forge.fusiondirectory.org/projects/fdirectory/issues/new> + +=head1 AUTHOR + +Come Bernigaud + +=head1 LICENCE AND COPYRIGHT + +This code is part of FusionDirectory <http://www.fusiondirectory.org> + +=over 1 + +=item Copyright (C) 2011-2017 FusionDirectory Project + +=back + +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. + +=cut