| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- # 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) 2009 Red Hat, Inc.
- # All rights reserved.
- # END COPYRIGHT BLOCK
- #
- ###########################
- #
- # This perl module provides code to update/upgrade directory
- # server shared files/config and instance specific files/config
- #
- ##########################
- package DSUpdate;
- use Util;
- use Inf;
- use FileConn;
- use DSCreate qw(setDefaults createInstanceScripts);
- use File::Basename qw(basename dirname);
- # 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 Exporter;
- @ISA = qw(Exporter);
- @EXPORT = qw(updateDS);
- @EXPORT_OK = qw(updateDS);
- use strict;
- use SetupLog;
- # the default location of the updates - this is a subdir
- # of the directory server data dir (e.g. /usr/share/dirsrv)
- # the default directory is read-only - if you need to provide
- # additional updates, pass in additional update directories
- # to updateDS
- my $DS_UPDATE_PATH = "@updatedir@";
- my $PRE_STAGE = "pre";
- my $PREINST_STAGE = "preinst";
- my $RUNINST_STAGE = "runinst";
- my $POSTINST_STAGE = "postinst";
- my $POST_STAGE = "post";
- my @STAGES = ($PRE_STAGE, $PREINST_STAGE, $RUNINST_STAGE, $POSTINST_STAGE, $POST_STAGE);
- my @INSTSTAGES = ($PREINST_STAGE, $RUNINST_STAGE, $POSTINST_STAGE);
- # used to create unique package names for loading updates
- # from perl scriptlets
- my $pkgname = "Package00000000000";
- # generate and return a unique package name that is a
- # subpackage of our current package
- sub get_pkgname {
- return __PACKAGE__ . "::" . $pkgname++;
- }
- sub loadUpdates {
- my $errs = shift;
- my $dirs = shift;
- my $mapinfo = shift || {};
- my @updates; # a list of hash refs, sorted in execution order
- for my $dir (@{$dirs}) {
- for my $file (glob("$dir/*")) {
- my $name = basename($file);
- next if $name !~ /^\d\d/; # we only consider files that begin with two digits
- # print "name = $name\n";
- my $href = { path => $file, name => $name };
- if ($file =~ /\.(pl|pm)$/) { # a perl file
- my $fullpkg = get_pkgname(); # get a unique package name for the file
- # this will import the update functions from the given file
- # each file is given its own private namespace via the package
- # directive below
- # we have to use the eval because package takes a "bareword" -
- # you cannot pass a dynamically constructed string to package
- eval "package $fullpkg; require q($file)"; # "import" it
- if ($@) {
- if ($@ =~ /did not return a true value/) {
- # this usually means the file did not end with 1; - just use it anyway
- debug(3, "notice: $file does not return a true value - using anyway\n");
- } else {
- # probably a syntax or other compilation error in the file
- # we can't safely use it, so log it and skip it
- push @{$errs}, ['error_loading_update', $file, $@];
- debug(0, "Error: not applying update $file. Error: $@\n");
- next; # skip this one
- }
- }
- # grab the hook functions from the update
- for my $fn (@STAGES) {
- # this is some deep perl magic - see the perl Symbol Table
- # documentation for the gory details
- # We're trying to find if the file defined a symbol called
- # pre, run, post, etc. and if so, if that symbol is code
- no strict 'refs'; # turn off strict refs to use magic
- if (*{$fullpkg . "::" . $fn}{CODE}) {
- debug(5, "$file $fn is defined\n");
- # store the "function pointer" in the href for this update
- $href->{$fn} = \&{$fullpkg . "::" . $fn};
- } else {
- debug(5, "$file $fn is not defined or not a subroutine\n");
- }
- }
- } else { # some other type of file
- $href->{file} = 1;
- }
- if ($mapinfo->{$file}) {
- $href->{mapper} = $mapinfo->{$file}->{mapper};
- $href->{infary} = $mapinfo->{$file}->{infary};
- }
- push @updates, $href;
- }
- }
- # we have all the updates now - sort by the name
- @updates = sort { $a->{name} cmp $b->{name} } @updates;
- return @updates;
- }
- sub applyLDIFUpdate {
- my ($upd, $conn, $inf) = @_;
- my @errs;
- my $path = ref($upd) ? $upd->{path} : $upd;
- my $mapper;
- my @infary;
- # caller can set mapper to use and additional inf to use
- if (ref($upd)) {
- if ($upd->{mapper}) {
- $mapper = new Inf($upd->{mapper});
- }
- if ($upd->{infary}) {
- @infary = @{$upd->{infary}};
- }
- }
- if (!$mapper) {
- $mapper = new Inf("$inf->{General}->{prefix}@infdir@/dsupdate.map");
- }
- my $dsinf = new Inf("$inf->{General}->{prefix}@infdir@/slapd.inf");
- $mapper = process_maptbl($mapper, \@errs, $inf, $dsinf, @infary);
- if (!$mapper or @errs) {
- return @errs;
- }
- getMappedEntries($mapper, [$path], \@errs, \&check_and_add_entry,
- [$conn]);
- return @errs;
- }
- # process an update from an ldif file or executable
- # LDIF files only apply to instance updates, so ignore
- # LDIF files when not processing updates for instances
- sub processUpdate {
- my ($upd, $inf, $configdir, $stage, $inst, $dseldif, $conn) = @_;
- my @errs;
- # $upd is either a hashref or a simple path name
- my $path = ref($upd) ? $upd->{path} : $upd;
- if ($path =~ /\.ldif$/) {
- # ldif files are only processed during the runinst stage
- if ($stage eq $RUNINST_STAGE) {
- @errs = applyLDIFUpdate($upd, $conn, $inf);
- }
- } elsif (-x $path) {
- # setup environment
- $ENV{DS_UPDATE_STAGE} = $stage;
- $ENV{DS_UPDATE_DIR} = $configdir;
- $ENV{DS_UPDATE_INST} = $inst; # empty if not instance specific
- $ENV{DS_UPDATE_DSELDIF} = $dseldif; # empty if not instance specific
- $? = 0; # clear error condition
- my $output = `$path 2>&1`;
- if ($?) {
- @errs = ('error_executing_update', $path, $?, $output);
- }
- debug(1, $output);
- } else {
- @errs = ('error_unknown_update', $path);
- }
- return @errs;
- }
- #
- sub updateDS {
- # get base configdir, instances from setup
- my $setup = shift;
- # get other info from inf
- my $inf = $setup->{inf};
- # directories containing updates to apply
- my $dirs = shift || [];
- my $mapinfo = shift;
- # the default directory server update path
- if ($inf->{slapd}->{updatedir}) {
- push @{$dirs}, $inf->{General}->{prefix} . $inf->{slapd}->{updatedir};
- } else {
- push @{$dirs}, $inf->{General}->{prefix} . $DS_UPDATE_PATH;
- }
- my @errs;
- my $force = $setup->{force};
- my @updates = loadUpdates(\@errs, $dirs, $mapinfo);
- if (@errs and !$force) {
- return @errs;
- }
- if (!@updates) {
- # nothing to do?
- debug(0, "No updates to apply in @{$dirs}\n");
- return @errs;
- }
- # run pre-update hooks
- for my $upd (@updates) {
- my @localerrs;
- if ($upd->{$PRE_STAGE}) {
- debug(1, "Running stage $PRE_STAGE update ", $upd->{path}, "\n");
- @localerrs = &{$upd->{$PRE_STAGE}}($inf, $setup->{configdir});
- } elsif ($upd->{file}) {
- debug(1, "Running stage $PRE_STAGE update ", $upd->{path}, "\n");
- @localerrs = processUpdate($upd, $inf, $setup->{configdir}, $PRE_STAGE);
- }
- if (@localerrs) {
- push @errs, @localerrs;
- if (!$force) {
- return @errs;
- }
- }
- }
- # update each instance
- for my $inst ($setup->getDirServers()) {
- my @localerrs = updateDSInstance($inst, $inf, $setup->{configdir}, \@updates, $force);
- if (@localerrs) {
- # push array here because localerrs will likely be an array of
- # array refs already
- push @errs, @localerrs;
- if (!$force) {
- return @errs;
- }
- }
- }
- # run post-update hooks
- for my $upd (@updates) {
- my @localerrs;
- if ($upd->{$POST_STAGE}) {
- debug(1, "Running stage $POST_STAGE update ", $upd->{path}, "\n");
- @localerrs = &{$upd->{$POST_STAGE}}($inf, $setup->{configdir});
- } elsif ($upd->{file}) {
- debug(1, "Running stage $POST_STAGE update ", $upd->{path}, "\n");
- @localerrs = processUpdate($upd, $inf, $setup->{configdir}, $POST_STAGE);
- }
- if (@localerrs) {
- push @errs, @localerrs;
- if (!$force) {
- return @errs;
- }
- }
- }
- return @errs;
- }
- sub updateDSInstance {
- my ($inst, $inf, $configdir, $updates, $force) = @_;
- my @errs;
- my $dseldif = "$configdir/$inst/dse.ldif";
- # get the information we need from the instance
- delete $inf->{slapd}; # delete old data, if any
- if (@errs = initInfFromInst($inf, $dseldif, $configdir, $inst)) {
- return @errs;
- }
- # upgrade instance scripts
- if (@errs = createInstanceScripts($inf, 1)) {
- return @errs;
- }
- my $conn;
- if ($inf->{General}->{UpdateMode} eq 'online') {
- # open a connection to the directory server to upgrade
- my $host = $inf->{General}->{FullMachineName};
- my $port = $inf->{slapd}->{ServerPort};
- # this says RootDN and password, but it can be any administrative DN
- # such as the one used by the console
- my $binddn = $inf->{$inst}->{RootDN} || $inf->{slapd}->{RootDN};
- my $bindpw = $inf->{$inst}->{RootDNPwd};
- my $certdir = $inf->{$inst}->{cert_dir} || $inf->{$inst}->{config_dir} || $inf->{slapd}->{cert_dir};
- $conn = new Mozilla::LDAP::Conn({ host => $host, port => $port, bind => $binddn,
- pswd => $bindpw, cert => $certdir, starttls => 1 });
- if (!$conn) {
- debug(0, "Could not open TLS connection to $host:$port - trying regular connection\n");
- $conn = new Mozilla::LDAP::Conn({ host => $host, port => $port, bind => $binddn,
- pswd => $bindpw });
- }
- if (!$conn) {
- debug(0, "Could not open a connection to $host:$port\n");
- return ('error_online_update', $host, $port, $binddn);
- }
- } else {
- $conn = new FileConn($dseldif);
- if (!$conn) {
- debug(0, "Could not open a connection to $dseldif: $!\n");
- return ('error_offline_update', $dseldif, $!);
- }
- }
- # run pre-instance hooks first, then runinst hooks, then postinst hooks
- # the DS_UPDATE_STAGE
- for my $stage (@INSTSTAGES) {
- # always process these first in the runinst stage - we don't really have any
- # other good way to process conditional features during update
- if ($stage eq $RUNINST_STAGE) {
- my @ldiffiles;
- if ("@enable_pam_passthru@") {
- push @ldiffiles, "$inf->{General}->{prefix}@templatedir@/template-pampta.ldif";
- }
- if ("@enable_bitwise@") {
- push @ldiffiles, "$inf->{General}->{prefix}@templatedir@/template-bitwise.ldif";
- }
- if ("@enable_dna@") {
- push @ldiffiles, "$inf->{General}->{prefix}@templatedir@/template-dnaplugin.ldif";
- push @ldiffiles, $inf->{General}->{prefix} . $DS_UPDATE_PATH . "/dnaplugindepends.ldif";
- }
- for my $ldiffile (@ldiffiles) {
- my @localerrs = processUpdate($ldiffile, $inf, $configdir, $stage,
- $inst, $dseldif, $conn);
- if (@localerrs) {
- push @errs, @localerrs;
- if (!$force) {
- $conn->close();
- return @errs;
- }
- }
- }
- }
- for my $upd (@{$updates}) {
- my @localerrs;
- if ($upd->{$stage}) {
- debug(1, "Running stage $stage update ", $upd->{path}, "\n");
- @localerrs = &{$upd->{$stage}}($inf, $inst, $dseldif, $conn);
- } elsif ($upd->{file}) {
- debug(1, "Running stage $stage update ", $upd->{path}, "\n");
- @localerrs = processUpdate($upd, $inf, $configdir, $stage,
- $inst, $dseldif, $conn);
- }
- if (@localerrs) {
- push @errs, @localerrs;
- if (!$force) {
- $conn->close();
- return @errs;
- }
- }
- }
- }
- $conn->close();
- return @errs;
- }
- # populate the fields in the inf we need to perform upgrade
- # tasks from the information in the instance dse.ldif and
- # other config
- sub initInfFromInst {
- my ($inf, $dseldif, $configdir, $inst) = @_;
- my $conn = new FileConn($dseldif, 1);
- if (!$conn) {
- debug(1, "Error: Could not open config file $dseldif: Error $!\n");
- return ('error_opening_dseldif', $dseldif, $!);
- }
- my $dn = "cn=config";
- my $entry = $conn->search($dn, "base", "(cn=*)", 0);
- if (!$entry) {
- $conn->close();
- debug(1, "Error: Search $dn in $dseldif failed: ".$conn->getErrorString()."\n");
- return ('error_finding_config_entry', $dn, $dseldif, $conn->getErrorString());
- }
- my $servid = $inst;
- $servid =~ s/slapd-//;
- $inf->{General}->{FullMachineName} = $entry->getValue("nsslapd-localhost");
- $inf->{General}->{SuiteSpotUserID} = $entry->getValue("nsslapd-localuser");
- $inf->{slapd}->{ServerPort} = $entry->getValue("nsslapd-port");
- $inf->{slapd}->{ldapifilepath} = $entry->getValue("nsslapd-ldapifilepath");
- if (!$inf->{$inst}->{RootDN}) {
- $inf->{$inst}->{RootDN} || $entry->getValue('nsslapd-rootdn');
- }
- # we don't use this password - we either use {$inst} password or
- # none at all
- $inf->{slapd}->{RootDNPwd} = '{SSHA}dummy';
- if (!$inf->{$inst}->{cert_dir}) {
- $inf->{$inst}->{cert_dir} = $entry->getValue('nsslapd-certdir');
- }
- $inf->{slapd}->{cert_dir} = $inf->{$inst}->{cert_dir};
- if (!$inf->{slapd}->{ldif_dir}) {
- $inf->{slapd}->{ldif_dir} = $entry->getValue('nsslapd-ldifdir');
- }
- if (!$inf->{slapd}->{ServerIdentifier}) {
- $inf->{slapd}->{ServerIdentifier} = $servid;
- }
- if (!$inf->{slapd}->{bak_dir}) {
- $inf->{slapd}->{bak_dir} = $entry->getValue('nsslapd-bakdir');
- }
- if (!$inf->{slapd}->{config_dir}) {
- $inf->{slapd}->{config_dir} = $configdir;
- }
- if (!$inf->{slapd}->{inst_dir}) {
- $inf->{slapd}->{inst_dir} = $entry->getValue('nsslapd-instancedir');
- }
- if (!$inf->{slapd}->{run_dir}) {
- $inf->{slapd}->{run_dir} = $entry->getValue('nsslapd-rundir');
- }
- if (!$inf->{slapd}->{schema_dir}) {
- $inf->{slapd}->{schema_dir} = $entry->getValue('nsslapd-schemadir');
- }
- if (!$inf->{slapd}->{lock_dir}) {
- $inf->{slapd}->{lock_dir} = $entry->getValue('nsslapd-lockdir');
- }
- if (!$inf->{slapd}->{log_dir}) {
- # use the errorlog dir
- my $logfile = $entry->getValue('nsslapd-errorlog');
- if ($logfile) {
- $inf->{slapd}->{log_dir} = dirname($logfile);
- }
- }
- if (!$inf->{slapd}->{sasl_path}) {
- $inf->{slapd}->{sasl_path} = $entry->getValue('nsslapd-saslpath');
- }
- # dn: cn=config,cn=ldbm database,cn=plugins,cn=config
- $dn = "cn=config,cn=ldbm database,cn=plugins,cn=config";
- $entry = $conn->search($dn, "base", "(cn=*)", 0);
- if (!$entry) {
- $conn->close();
- debug(1, "Error: Search $dn in $dseldif failed: ".$conn->getErrorString()."\n");
- return ('error_finding_config_entry', $dn, $dseldif, $conn->getErrorString());
- }
- if (!$inf->{slapd}->{db_dir}) {
- $inf->{slapd}->{db_dir} = $entry->getValue('nsslapd-directory');
- }
- $conn->close(); # don't need this anymore
- # set defaults for things we don't know how to find, after setting the values
- # we do know how to find
- return setDefaults($inf);
- }
- 1;
- # emacs settings
- # Local Variables:
- # mode:perl
- # indent-tabs-mode: nil
- # tab-width: 4
- # End:
|