1
0

DSUpdate.pm.in 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. # BEGIN COPYRIGHT BLOCK
  2. # This Program is free software; you can redistribute it and/or modify it under
  3. # the terms of the GNU General Public License as published by the Free Software
  4. # Foundation; version 2 of the License.
  5. #
  6. # This Program is distributed in the hope that it will be useful, but WITHOUT
  7. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  8. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  9. #
  10. # You should have received a copy of the GNU General Public License along with
  11. # this Program; if not, write to the Free Software Foundation, Inc., 59 Temple
  12. # Place, Suite 330, Boston, MA 02111-1307 USA.
  13. #
  14. # In addition, as a special exception, Red Hat, Inc. gives You the additional
  15. # right to link the code of this Program with code not covered under the GNU
  16. # General Public License ("Non-GPL Code") and to distribute linked combinations
  17. # including the two, subject to the limitations in this paragraph. Non-GPL Code
  18. # permitted under this exception must only link to the code of this Program
  19. # through those well defined interfaces identified in the file named EXCEPTION
  20. # found in the source code files (the "Approved Interfaces"). The files of
  21. # Non-GPL Code may instantiate templates or use macros or inline functions from
  22. # the Approved Interfaces without causing the resulting work to be covered by
  23. # the GNU General Public License. Only Red Hat, Inc. may make changes or
  24. # additions to the list of Approved Interfaces. You must obey the GNU General
  25. # Public License in all respects for all of the Program code and other code used
  26. # in conjunction with the Program except the Non-GPL Code covered by this
  27. # exception. If you modify this file, you may extend this exception to your
  28. # version of the file, but you are not obligated to do so. If you do not wish to
  29. # provide this exception without modification, you must delete this exception
  30. # statement from your version and license this file solely under the GPL without
  31. # exception.
  32. #
  33. #
  34. # Copyright (C) 2009 Red Hat, Inc.
  35. # All rights reserved.
  36. # END COPYRIGHT BLOCK
  37. #
  38. ###########################
  39. #
  40. # This perl module provides code to update/upgrade directory
  41. # server shared files/config and instance specific files/config
  42. #
  43. ##########################
  44. package DSUpdate;
  45. use Util;
  46. use Inf;
  47. use FileConn;
  48. use DSCreate qw(setDefaults createInstanceScripts);
  49. use File::Basename qw(basename dirname);
  50. # load perldap
  51. use Mozilla::LDAP::Conn;
  52. use Mozilla::LDAP::Utils qw(normalizeDN);
  53. use Mozilla::LDAP::API qw(ldap_explode_dn);
  54. use Mozilla::LDAP::LDIF;
  55. use Exporter;
  56. @ISA = qw(Exporter);
  57. @EXPORT = qw(updateDS);
  58. @EXPORT_OK = qw(updateDS);
  59. use strict;
  60. use SetupLog;
  61. # the default location of the updates - this is a subdir
  62. # of the directory server data dir (e.g. /usr/share/dirsrv)
  63. # the default directory is read-only - if you need to provide
  64. # additional updates, pass in additional update directories
  65. # to updateDS
  66. my $DS_UPDATE_PATH = "@updatedir@";
  67. my $PRE_STAGE = "pre";
  68. my $PREINST_STAGE = "preinst";
  69. my $RUNINST_STAGE = "runinst";
  70. my $POSTINST_STAGE = "postinst";
  71. my $POST_STAGE = "post";
  72. my @STAGES = ($PRE_STAGE, $PREINST_STAGE, $RUNINST_STAGE, $POSTINST_STAGE, $POST_STAGE);
  73. my @INSTSTAGES = ($PREINST_STAGE, $RUNINST_STAGE, $POSTINST_STAGE);
  74. # used to create unique package names for loading updates
  75. # from perl scriptlets
  76. my $pkgname = "Package00000000000";
  77. # generate and return a unique package name that is a
  78. # subpackage of our current package
  79. sub get_pkgname {
  80. return __PACKAGE__ . "::" . $pkgname++;
  81. }
  82. sub loadUpdates {
  83. my $errs = shift;
  84. my $dirs = shift;
  85. my $mapinfo = shift || {};
  86. my @updates; # a list of hash refs, sorted in execution order
  87. for my $dir (@{$dirs}) {
  88. for my $file (glob("$dir/*")) {
  89. my $name = basename($file);
  90. next if $name !~ /^\d\d/; # we only consider files that begin with two digits
  91. # print "name = $name\n";
  92. my $href = { path => $file, name => $name };
  93. if ($file =~ /\.(pl|pm)$/) { # a perl file
  94. my $fullpkg = get_pkgname(); # get a unique package name for the file
  95. # this will import the update functions from the given file
  96. # each file is given its own private namespace via the package
  97. # directive below
  98. # we have to use the eval because package takes a "bareword" -
  99. # you cannot pass a dynamically constructed string to package
  100. eval "package $fullpkg; require q($file)"; # "import" it
  101. if ($@) {
  102. if ($@ =~ /did not return a true value/) {
  103. # this usually means the file did not end with 1; - just use it anyway
  104. debug(3, "notice: $file does not return a true value - using anyway\n");
  105. } else {
  106. # probably a syntax or other compilation error in the file
  107. # we can't safely use it, so log it and skip it
  108. push @{$errs}, ['error_loading_update', $file, $@];
  109. debug(0, "Error: not applying update $file. Error: $@\n");
  110. next; # skip this one
  111. }
  112. }
  113. # grab the hook functions from the update
  114. for my $fn (@STAGES) {
  115. # this is some deep perl magic - see the perl Symbol Table
  116. # documentation for the gory details
  117. # We're trying to find if the file defined a symbol called
  118. # pre, run, post, etc. and if so, if that symbol is code
  119. no strict 'refs'; # turn off strict refs to use magic
  120. if (*{$fullpkg . "::" . $fn}{CODE}) {
  121. debug(5, "$file $fn is defined\n");
  122. # store the "function pointer" in the href for this update
  123. $href->{$fn} = \&{$fullpkg . "::" . $fn};
  124. } else {
  125. debug(5, "$file $fn is not defined or not a subroutine\n");
  126. }
  127. }
  128. } else { # some other type of file
  129. $href->{file} = 1;
  130. }
  131. if ($mapinfo->{$file}) {
  132. $href->{mapper} = $mapinfo->{$file}->{mapper};
  133. $href->{infary} = $mapinfo->{$file}->{infary};
  134. }
  135. push @updates, $href;
  136. }
  137. }
  138. # we have all the updates now - sort by the name
  139. @updates = sort { $a->{name} cmp $b->{name} } @updates;
  140. return @updates;
  141. }
  142. sub applyLDIFUpdate {
  143. my ($upd, $conn, $inf) = @_;
  144. my @errs;
  145. my $path = ref($upd) ? $upd->{path} : $upd;
  146. my $mapper;
  147. my @infary;
  148. # caller can set mapper to use and additional inf to use
  149. if (ref($upd)) {
  150. if ($upd->{mapper}) {
  151. $mapper = new Inf($upd->{mapper});
  152. }
  153. if ($upd->{infary}) {
  154. @infary = @{$upd->{infary}};
  155. }
  156. }
  157. if (!$mapper) {
  158. $mapper = new Inf("$inf->{General}->{prefix}@infdir@/dsupdate.map");
  159. }
  160. my $dsinf = new Inf("$inf->{General}->{prefix}@infdir@/slapd.inf");
  161. $mapper = process_maptbl($mapper, \@errs, $inf, $dsinf, @infary);
  162. if (!$mapper or @errs) {
  163. return @errs;
  164. }
  165. getMappedEntries($mapper, [$path], \@errs, \&check_and_add_entry,
  166. [$conn]);
  167. return @errs;
  168. }
  169. # process an update from an ldif file or executable
  170. # LDIF files only apply to instance updates, so ignore
  171. # LDIF files when not processing updates for instances
  172. sub processUpdate {
  173. my ($upd, $inf, $configdir, $stage, $inst, $dseldif, $conn) = @_;
  174. my @errs;
  175. # $upd is either a hashref or a simple path name
  176. my $path = ref($upd) ? $upd->{path} : $upd;
  177. if ($path =~ /\.ldif$/) {
  178. # ldif files are only processed during the runinst stage
  179. if ($stage eq $RUNINST_STAGE) {
  180. @errs = applyLDIFUpdate($upd, $conn, $inf);
  181. }
  182. } elsif (-x $path) {
  183. # setup environment
  184. $ENV{DS_UPDATE_STAGE} = $stage;
  185. $ENV{DS_UPDATE_DIR} = $configdir;
  186. $ENV{DS_UPDATE_INST} = $inst; # empty if not instance specific
  187. $ENV{DS_UPDATE_DSELDIF} = $dseldif; # empty if not instance specific
  188. $? = 0; # clear error condition
  189. my $output = `$path 2>&1`;
  190. if ($?) {
  191. @errs = ('error_executing_update', $path, $?, $output);
  192. }
  193. debug(1, $output);
  194. } else {
  195. @errs = ('error_unknown_update', $path);
  196. }
  197. return @errs;
  198. }
  199. #
  200. sub updateDS {
  201. # get base configdir, instances from setup
  202. my $setup = shift;
  203. # get other info from inf
  204. my $inf = $setup->{inf};
  205. # directories containing updates to apply
  206. my $dirs = shift || [];
  207. my $mapinfo = shift;
  208. # the default directory server update path
  209. if ($inf->{slapd}->{updatedir}) {
  210. push @{$dirs}, $inf->{General}->{prefix} . $inf->{slapd}->{updatedir};
  211. } else {
  212. push @{$dirs}, $inf->{General}->{prefix} . $DS_UPDATE_PATH;
  213. }
  214. my @errs;
  215. my $force = $setup->{force};
  216. my @updates = loadUpdates(\@errs, $dirs, $mapinfo);
  217. if (@errs and !$force) {
  218. return @errs;
  219. }
  220. if (!@updates) {
  221. # nothing to do?
  222. debug(0, "No updates to apply in @{$dirs}\n");
  223. return @errs;
  224. }
  225. # run pre-update hooks
  226. for my $upd (@updates) {
  227. my @localerrs;
  228. if ($upd->{$PRE_STAGE}) {
  229. debug(1, "Running stage $PRE_STAGE update ", $upd->{path}, "\n");
  230. @localerrs = &{$upd->{$PRE_STAGE}}($inf, $setup->{configdir});
  231. } elsif ($upd->{file}) {
  232. debug(1, "Running stage $PRE_STAGE update ", $upd->{path}, "\n");
  233. @localerrs = processUpdate($upd, $inf, $setup->{configdir}, $PRE_STAGE);
  234. }
  235. if (@localerrs) {
  236. push @errs, @localerrs;
  237. if (!$force) {
  238. return @errs;
  239. }
  240. }
  241. }
  242. # update each instance
  243. for my $inst ($setup->getDirServers()) {
  244. my @localerrs = updateDSInstance($inst, $inf, $setup->{configdir}, \@updates, $force);
  245. if (@localerrs) {
  246. # push array here because localerrs will likely be an array of
  247. # array refs already
  248. push @errs, @localerrs;
  249. if (!$force) {
  250. return @errs;
  251. }
  252. }
  253. }
  254. # run post-update hooks
  255. for my $upd (@updates) {
  256. my @localerrs;
  257. if ($upd->{$POST_STAGE}) {
  258. debug(1, "Running stage $POST_STAGE update ", $upd->{path}, "\n");
  259. @localerrs = &{$upd->{$POST_STAGE}}($inf, $setup->{configdir});
  260. } elsif ($upd->{file}) {
  261. debug(1, "Running stage $POST_STAGE update ", $upd->{path}, "\n");
  262. @localerrs = processUpdate($upd, $inf, $setup->{configdir}, $POST_STAGE);
  263. }
  264. if (@localerrs) {
  265. push @errs, @localerrs;
  266. if (!$force) {
  267. return @errs;
  268. }
  269. }
  270. }
  271. return @errs;
  272. }
  273. sub updateDSInstance {
  274. my ($inst, $inf, $configdir, $updates, $force) = @_;
  275. my @errs;
  276. my $dseldif = "$configdir/$inst/dse.ldif";
  277. # get the information we need from the instance
  278. delete $inf->{slapd}; # delete old data, if any
  279. if (@errs = initInfFromInst($inf, $dseldif, $configdir, $inst)) {
  280. return @errs;
  281. }
  282. # upgrade instance scripts
  283. if (@errs = createInstanceScripts($inf, 1)) {
  284. return @errs;
  285. }
  286. my $conn;
  287. if ($inf->{General}->{UpdateMode} eq 'online') {
  288. # open a connection to the directory server to upgrade
  289. my $host = $inf->{General}->{FullMachineName};
  290. my $port = $inf->{slapd}->{ServerPort};
  291. # this says RootDN and password, but it can be any administrative DN
  292. # such as the one used by the console
  293. my $binddn = $inf->{$inst}->{RootDN} || $inf->{slapd}->{RootDN};
  294. my $bindpw = $inf->{$inst}->{RootDNPwd};
  295. my $certdir = $inf->{$inst}->{cert_dir} || $inf->{$inst}->{config_dir} || $inf->{slapd}->{cert_dir};
  296. $conn = new Mozilla::LDAP::Conn({ host => $host, port => $port, bind => $binddn,
  297. pswd => $bindpw, cert => $certdir, starttls => 1 });
  298. if (!$conn) {
  299. debug(0, "Could not open TLS connection to $host:$port - trying regular connection\n");
  300. $conn = new Mozilla::LDAP::Conn({ host => $host, port => $port, bind => $binddn,
  301. pswd => $bindpw });
  302. }
  303. if (!$conn) {
  304. debug(0, "Could not open a connection to $host:$port\n");
  305. return ('error_online_update', $host, $port, $binddn);
  306. }
  307. } else {
  308. $conn = new FileConn($dseldif);
  309. if (!$conn) {
  310. debug(0, "Could not open a connection to $dseldif: $!\n");
  311. return ('error_offline_update', $dseldif, $!);
  312. }
  313. }
  314. # run pre-instance hooks first, then runinst hooks, then postinst hooks
  315. # the DS_UPDATE_STAGE
  316. for my $stage (@INSTSTAGES) {
  317. # always process these first in the runinst stage - we don't really have any
  318. # other good way to process conditional features during update
  319. if ($stage eq $RUNINST_STAGE) {
  320. my @ldiffiles;
  321. if ("@enable_pam_passthru@") {
  322. push @ldiffiles, "$inf->{General}->{prefix}@templatedir@/template-pampta.ldif";
  323. }
  324. if ("@enable_bitwise@") {
  325. push @ldiffiles, "$inf->{General}->{prefix}@templatedir@/template-bitwise.ldif";
  326. }
  327. if ("@enable_dna@") {
  328. push @ldiffiles, "$inf->{General}->{prefix}@templatedir@/template-dnaplugin.ldif";
  329. push @ldiffiles, $inf->{General}->{prefix} . $DS_UPDATE_PATH . "/dnaplugindepends.ldif";
  330. }
  331. for my $ldiffile (@ldiffiles) {
  332. my @localerrs = processUpdate($ldiffile, $inf, $configdir, $stage,
  333. $inst, $dseldif, $conn);
  334. if (@localerrs) {
  335. push @errs, @localerrs;
  336. if (!$force) {
  337. $conn->close();
  338. return @errs;
  339. }
  340. }
  341. }
  342. }
  343. for my $upd (@{$updates}) {
  344. my @localerrs;
  345. if ($upd->{$stage}) {
  346. debug(1, "Running stage $stage update ", $upd->{path}, "\n");
  347. @localerrs = &{$upd->{$stage}}($inf, $inst, $dseldif, $conn);
  348. } elsif ($upd->{file}) {
  349. debug(1, "Running stage $stage update ", $upd->{path}, "\n");
  350. @localerrs = processUpdate($upd, $inf, $configdir, $stage,
  351. $inst, $dseldif, $conn);
  352. }
  353. if (@localerrs) {
  354. push @errs, @localerrs;
  355. if (!$force) {
  356. $conn->close();
  357. return @errs;
  358. }
  359. }
  360. }
  361. }
  362. $conn->close();
  363. return @errs;
  364. }
  365. # populate the fields in the inf we need to perform upgrade
  366. # tasks from the information in the instance dse.ldif and
  367. # other config
  368. sub initInfFromInst {
  369. my ($inf, $dseldif, $configdir, $inst) = @_;
  370. my $conn = new FileConn($dseldif, 1);
  371. if (!$conn) {
  372. debug(1, "Error: Could not open config file $dseldif: Error $!\n");
  373. return ('error_opening_dseldif', $dseldif, $!);
  374. }
  375. my $dn = "cn=config";
  376. my $entry = $conn->search($dn, "base", "(cn=*)", 0);
  377. if (!$entry) {
  378. $conn->close();
  379. debug(1, "Error: Search $dn in $dseldif failed: ".$conn->getErrorString()."\n");
  380. return ('error_finding_config_entry', $dn, $dseldif, $conn->getErrorString());
  381. }
  382. my $servid = $inst;
  383. $servid =~ s/slapd-//;
  384. $inf->{General}->{FullMachineName} = $entry->getValue("nsslapd-localhost");
  385. $inf->{General}->{SuiteSpotUserID} = $entry->getValue("nsslapd-localuser");
  386. $inf->{slapd}->{ServerPort} = $entry->getValue("nsslapd-port");
  387. $inf->{slapd}->{ldapifilepath} = $entry->getValue("nsslapd-ldapifilepath");
  388. if (!$inf->{$inst}->{RootDN}) {
  389. $inf->{$inst}->{RootDN} || $entry->getValue('nsslapd-rootdn');
  390. }
  391. # we don't use this password - we either use {$inst} password or
  392. # none at all
  393. $inf->{slapd}->{RootDNPwd} = '{SSHA}dummy';
  394. if (!$inf->{$inst}->{cert_dir}) {
  395. $inf->{$inst}->{cert_dir} = $entry->getValue('nsslapd-certdir');
  396. }
  397. $inf->{slapd}->{cert_dir} = $inf->{$inst}->{cert_dir};
  398. if (!$inf->{slapd}->{ldif_dir}) {
  399. $inf->{slapd}->{ldif_dir} = $entry->getValue('nsslapd-ldifdir');
  400. }
  401. if (!$inf->{slapd}->{ServerIdentifier}) {
  402. $inf->{slapd}->{ServerIdentifier} = $servid;
  403. }
  404. if (!$inf->{slapd}->{bak_dir}) {
  405. $inf->{slapd}->{bak_dir} = $entry->getValue('nsslapd-bakdir');
  406. }
  407. if (!$inf->{slapd}->{config_dir}) {
  408. $inf->{slapd}->{config_dir} = $configdir;
  409. }
  410. if (!$inf->{slapd}->{inst_dir}) {
  411. $inf->{slapd}->{inst_dir} = $entry->getValue('nsslapd-instancedir');
  412. }
  413. if (!$inf->{slapd}->{run_dir}) {
  414. $inf->{slapd}->{run_dir} = $entry->getValue('nsslapd-rundir');
  415. }
  416. if (!$inf->{slapd}->{schema_dir}) {
  417. $inf->{slapd}->{schema_dir} = $entry->getValue('nsslapd-schemadir');
  418. }
  419. if (!$inf->{slapd}->{lock_dir}) {
  420. $inf->{slapd}->{lock_dir} = $entry->getValue('nsslapd-lockdir');
  421. }
  422. if (!$inf->{slapd}->{log_dir}) {
  423. # use the errorlog dir
  424. my $logfile = $entry->getValue('nsslapd-errorlog');
  425. if ($logfile) {
  426. $inf->{slapd}->{log_dir} = dirname($logfile);
  427. }
  428. }
  429. if (!$inf->{slapd}->{sasl_path}) {
  430. $inf->{slapd}->{sasl_path} = $entry->getValue('nsslapd-saslpath');
  431. }
  432. # dn: cn=config,cn=ldbm database,cn=plugins,cn=config
  433. $dn = "cn=config,cn=ldbm database,cn=plugins,cn=config";
  434. $entry = $conn->search($dn, "base", "(cn=*)", 0);
  435. if (!$entry) {
  436. $conn->close();
  437. debug(1, "Error: Search $dn in $dseldif failed: ".$conn->getErrorString()."\n");
  438. return ('error_finding_config_entry', $dn, $dseldif, $conn->getErrorString());
  439. }
  440. if (!$inf->{slapd}->{db_dir}) {
  441. $inf->{slapd}->{db_dir} = $entry->getValue('nsslapd-directory');
  442. }
  443. $conn->close(); # don't need this anymore
  444. # set defaults for things we don't know how to find, after setting the values
  445. # we do know how to find
  446. return setDefaults($inf);
  447. }
  448. 1;
  449. # emacs settings
  450. # Local Variables:
  451. # mode:perl
  452. # indent-tabs-mode: nil
  453. # tab-width: 4
  454. # End: