Util.pm.in 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  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) 2007 Red Hat, Inc.
  35. # All rights reserved.
  36. # END COPYRIGHT BLOCK
  37. #
  38. package Util;
  39. use Mozilla::LDAP::Conn;
  40. use Mozilla::LDAP::Utils qw(normalizeDN);
  41. use Mozilla::LDAP::API qw(:constant ldap_explode_dn ldap_err2string) ; # Direct access to C API
  42. use Mozilla::LDAP::LDIF;
  43. require Exporter;
  44. @ISA = qw(Exporter);
  45. @EXPORT = qw(portAvailable getAvailablePort isValidDN addSuffix getMappedEntries
  46. process_maptbl check_and_add_entry getMappedEntries
  47. getHashedPassword debug createInfFromConfig shellEscape
  48. isValidServerID isValidUser makePaths getLogin remove_tree remove_pidfile);
  49. @EXPORT_OK = qw(portAvailable getAvailablePort isValidDN addSuffix getMappedEntries
  50. process_maptbl check_and_add_entry getMappedEntries
  51. getHashedPassword debug createInfFromConfig shellEscape
  52. isValidServerID isValidUser makePaths getLogin remove_tree remove_pidfile);
  53. use strict;
  54. use Socket;
  55. use File::Temp qw(tempfile tempdir);
  56. use File::Basename qw(dirname);
  57. use File::Path qw(rmtree);
  58. use Carp;
  59. $Util::debuglevel = 0;
  60. # use like this:
  61. # debug(3, "message");
  62. # this will only print "message" if $debuglevel is 3 or higher (-ddd on the command line)
  63. sub debug {
  64. my ($level, @rest) = @_;
  65. if ($level <= $Util::debuglevel) {
  66. print STDERR "+" x $level, @rest;
  67. }
  68. }
  69. # return true if the given port number is available, false otherwise
  70. sub portAvailable {
  71. my $port = shift;
  72. my $proto = getprotobyname('tcp');
  73. my $rc = socket(SOCK, PF_INET, SOCK_STREAM, $proto);
  74. if ($rc == 1) {
  75. setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, 1);
  76. $rc = bind(SOCK, sockaddr_in($port, INADDR_ANY));
  77. }
  78. close(SOCK);
  79. return $rc and ($rc == 1);
  80. }
  81. # returns a randomly assigned port number, or -1
  82. # if not able to find an available port
  83. sub getAvailablePort {
  84. my $MINPORT = 1024;
  85. my $MAXPORT = 65535;
  86. srand( time() ^ ($$ + ($$ << 15)) );
  87. while (1) {
  88. my $port = $MINPORT + int(rand($MAXPORT-$MINPORT));
  89. if (portAvailable($port)) {
  90. return $port;
  91. }
  92. }
  93. }
  94. sub isValidDN {
  95. my $dn = shift;
  96. return ($dn =~ /^[0-9a-zA-Z_-]+=.*$/);
  97. }
  98. sub isValidServerID {
  99. my $servid = shift;
  100. my $validchars = '#%:\w@_-';
  101. return $servid =~ /^[$validchars]+$/o;
  102. }
  103. # we want the name of the effective user id of this process e.g. if someone did
  104. # an su root, we want getLogin to return "root" not the originating id (getlogin)
  105. # in perl, $> is the effective numeric user id - we need to turn it into a string
  106. # use confess here because if we cannot determine the user, something is really,
  107. # really wrong and we need to abort immediately
  108. sub getLogin {
  109. return (getpwuid($>))[0] || $ENV{USER} || confess "Error: could not determine the current user ID: $!";
  110. }
  111. sub isValidUser {
  112. my $user = shift;
  113. # convert numeric uid to string
  114. my $strans = $user;
  115. if ($user =~ /^\d+$/) { # numeric - convert to string
  116. $strans = getpwuid $user;
  117. if (!$strans) {
  118. return ("dialog_ssuser_error", $user);
  119. }
  120. }
  121. if ($> != 0) { # if not root, the user must be our uid
  122. my $username = getLogin;
  123. if ($strans ne $username) {
  124. return ("dialog_ssuser_must_be_same", $username);
  125. }
  126. } else { # user is root - verify id
  127. my $nuid = getpwnam $strans;
  128. if (!defined($nuid)) {
  129. return ("dialog_ssuser_error", $user);
  130. }
  131. if (!$nuid) {
  132. debug(0, "Warning: using root as the server user id. You are strongly encouraged to use a non-root user.\n");
  133. }
  134. }
  135. return ();
  136. }
  137. # delete the subtree starting from the passed entry
  138. sub delete_all
  139. {
  140. my ($conn, $bentry) = @_;
  141. my $sentry = $conn->search($bentry->{dn},
  142. "subtree", "(objectclass=*)", 0, ("dn"));
  143. my @mystack = ();
  144. while ($sentry) {
  145. push @mystack, $sentry->getDN();
  146. $sentry = $conn->nextEntry();
  147. }
  148. # reverse order
  149. my $dn = pop @mystack;
  150. while ($dn) {
  151. $conn->delete($dn);
  152. my $rc = $conn->getErrorCode();
  153. if ( $rc != 0 ) {
  154. debug(1, "ERROR: unable to delete entry $dn, error code: $rc:" . $conn->getErrorString() . "\n");
  155. return 1;
  156. }
  157. $dn = pop @mystack;
  158. }
  159. return 0;
  160. }
  161. # if the entry does not exist on the server, add the entry.
  162. # otherwise, do nothing
  163. # you can use this as the callback to getMappedEntries, so
  164. # that for each entry in the ldif file being processed, you
  165. # can call this subroutine to add or update the entry
  166. # use like this:
  167. # getMappedEntries($mapper, \@ldiffiles, \&check_and_add_entry,
  168. # [$conn, $fresh, $verbose]);
  169. # where $conn is a perldap Conn
  170. # $fresh if true will update the entry if it exists
  171. # $verbose prints out more info
  172. sub check_and_add_entry
  173. {
  174. my ($context, $aentry, $errs) = @_;
  175. my $conn = $context->[0];
  176. my $fresh = $context->[1];
  177. my $verbose = $context->[2];
  178. my @ctypes = $aentry->getValues("changetype");
  179. my $sentry = $conn->search($aentry->{dn}, "base", "(objectclass=*)", 0, ("*", "aci"));
  180. if ($sentry) {
  181. debug(3, "check_and_add_entry: Found entry " . $sentry->getDN() . "\n");
  182. if (! @ctypes) { # entry exists, and this is not a modify op
  183. debug(3, "check_and_add_entry: skipping entry " . $sentry->getDN() . "\n");
  184. return 1; # ignore - return success
  185. }
  186. } else {
  187. debug(3, "check_and_add_entry: Entry not found " . $aentry->{dn} .
  188. " error " . $conn->getErrorString() . "\n");
  189. if (@ctypes) { # uh oh - attempt to del/mod an entry that doesn't exist
  190. debug(3, "check_and_add_entry: attepting to @ctypes the entry " . $aentry->{dn} .
  191. " that does not exist\n");
  192. return 1; # ignore - return success
  193. }
  194. }
  195. do
  196. {
  197. my @addtypes; # list of attr types for mod add
  198. my @reptypes; # list of attr types for mod replace
  199. my @deltypes; # list of attr types for mod delete
  200. my $OP_NONE = 0;
  201. my $OP_ADD = 1;
  202. my $OP_MOD = 2;
  203. my $OP_DEL = 3;
  204. # $op stores either of the above $OP_ values
  205. my $op = $OP_NONE;
  206. if ( 0 > $#ctypes ) # aentry: complete entry
  207. {
  208. $op = $OP_ADD; # just add the entry
  209. }
  210. else # aentry: modify format
  211. {
  212. if ( $sentry )
  213. {
  214. if ( "delete" eq lc($ctypes[0]) )
  215. {
  216. $op = $OP_DEL;
  217. }
  218. else
  219. {
  220. @addtypes = $aentry->getValues("add");
  221. @reptypes = $aentry->getValues("replace");
  222. @deltypes = $aentry->getValues("delete");
  223. $op = $OP_MOD;
  224. }
  225. }
  226. else
  227. {
  228. $op = $OP_NONE;
  229. }
  230. }
  231. if ( $OP_ADD == $op )
  232. {
  233. $conn->add($aentry);
  234. my $rc = $conn->getErrorCode();
  235. if ( $rc != 0 )
  236. {
  237. my $string = $conn->getErrorString();
  238. push @{$errs}, 'error_adding_entry', $aentry->{dn}, $string;
  239. debug(1, "ERROR: adding an entry $aentry->{dn} failed, error: $string\n");
  240. $aentry->printLDIF();
  241. $conn->close();
  242. return 0;
  243. }
  244. debug(1, "Entry $aentry->{dn} is added\n");
  245. }
  246. elsif ( $OP_DEL == $op )
  247. {
  248. my $rc = delete_all($conn, $sentry);
  249. if ( 0 != $rc )
  250. {
  251. push @{$errs}, 'error_deleteall_entries', $sentry->{dn}, $conn->getErrorString();
  252. debug(1, "Error deleting $sentry->{dn}\n");
  253. return 0;
  254. }
  255. debug(1, "Entry $aentry->{dn} is deleted\n");
  256. }
  257. elsif ( 0 < $op ) # modify op
  258. {
  259. my $attr;
  260. my @errsToIgnore;
  261. if (@addtypes) {
  262. push @errsToIgnore, LDAP_TYPE_OR_VALUE_EXISTS;
  263. }
  264. foreach $attr ( @addtypes )
  265. {
  266. foreach my $val ($aentry->getValues($attr))
  267. {
  268. debug(3, "Adding attr=$attr value=$val to entry $aentry->{dn}\n");
  269. $sentry->addValue( $attr, $val );
  270. }
  271. }
  272. foreach $attr ( @reptypes )
  273. {
  274. my @vals = $aentry->getValues($attr);
  275. debug(3, "Replacing attr=$attr values=" . $aentry->getValues($attr) . " to entry $aentry->{dn}\n");
  276. $sentry->setValues($attr, @vals);
  277. }
  278. if (@deltypes) {
  279. push @errsToIgnore, LDAP_NO_SUCH_ATTRIBUTE;
  280. }
  281. foreach $attr ( @deltypes )
  282. {
  283. # removeValue takes a single value only
  284. if (!$aentry->size($attr))
  285. {
  286. debug(3, "Deleting attr=$attr from entry $aentry->{dn}\n");
  287. $sentry->remove($attr); # just delete the attribute
  288. }
  289. else
  290. {
  291. debug(3, "Deleting attr=$attr values=" . $aentry->getValues($attr) . " from entry $aentry->{dn}\n");
  292. foreach my $val ($aentry->getValues($attr))
  293. {
  294. $sentry->removeValue($attr, $val);
  295. }
  296. }
  297. }
  298. $conn->update($sentry);
  299. my $rc = $conn->getErrorCode();
  300. if ( $rc != 0 )
  301. {
  302. my $string = $conn->getErrorString();
  303. debug(1, "ERROR: updating an entry $sentry->{dn} failed, error: $string\n");
  304. if (grep /^$rc$/, @errsToIgnore) {
  305. debug(1, "Ignoring error $rc returned by adding @addtypes deleting @deltypes\n");
  306. } else {
  307. push @{$errs}, 'error_updating_entry', $sentry->{dn}, $string;
  308. $aentry->printLDIF();
  309. $conn->close();
  310. return 0;
  311. }
  312. }
  313. }
  314. if ( $sentry )
  315. {
  316. $sentry = $conn->nextEntry(); # supposed to have no more entries
  317. }
  318. } until ( !$sentry );
  319. out:
  320. return 1;
  321. }
  322. # the default callback used with getMappedEntries
  323. # just adds the given entry to the given list
  324. sub cbaddent {
  325. my $list = shift;
  326. my $ent = shift;
  327. push @{$list}, $ent;
  328. return 1;
  329. }
  330. # given a mapper and a list of LDIF files, produce a list of
  331. # perldap Entry objects which have had their tokens subst-ed
  332. # with values from the mapper
  333. # An optional callback can be supplied. Each entry will be
  334. # given to this callback. The callback should return a list
  335. # of localizable errors. If no callback is supplied, the
  336. # entries will be returned in a list.
  337. # Arguments:
  338. # mapper - a hash ref - the keys are the tokens to replace
  339. # and the values are the replacements
  340. # ldiffiles - an array ref - the list of LDIF files to
  341. # operate on
  342. # errs - an array ref - this is filled in with the
  343. # errors encountered in processing - this is
  344. # suitable for passing to setup->msg or
  345. # Resource->getText
  346. # callback (optional) - a code ref - a ref to a subroutine
  347. # that will be called with each entry - see below
  348. # context (optional) - this will be passed as the first
  349. # argument to your given callback - see below
  350. # Callback:
  351. # The callback sub will be called for each entry after
  352. # the entry has been converted. The callback will be
  353. # called with the given context as the first argument
  354. # and the Mozilla::LDAP::Entry as the second argument,
  355. # and an errs array ref as the third argument. The
  356. # callback should return true to continue processing,
  357. # or false if a fatal error was encountered that should
  358. # abort processing of any further.
  359. # Errors:
  360. # This function should return an array of errors in the
  361. # format described below, for use with Resource::getText()
  362. # or Setup::msg()
  363. # Return:
  364. # The return value is a list of entries.
  365. # Example usage:
  366. # sub handle_entries {
  367. # my $context = shift;
  368. # my $entry = shift;
  369. # my $errs = shift;
  370. # .... do something with entry ....
  371. # .... if $context is Mozilla::LDAP::Conn, $conn->add($entry); ...
  372. # .... report errors ....
  373. # if ($fatalerror) {
  374. # push @{$errs}, 'error_token', arg1, arg2, ...;
  375. # return 0;
  376. # } else {
  377. # return 1;
  378. # }
  379. # }
  380. # $mapper = {foo => 'bar', baz => 'biff'};
  381. # @ldiffiles = ('foo.ldif', 'bar.ldif', ..., 'biff.ldif');
  382. # $conn = new Mozilla::LDAP::Conn(...);
  383. # my @errs;
  384. # @entries = getMappedEntries($mapper, \@ldiffiles, \@errs, \&handle_entries, $conn);
  385. # Note that this will return 0 entries since a callback was used.
  386. # The simpler example is this:
  387. # @entries = getMappedEntries($mapper, \@ldiffiles, \@errs);
  388. #
  389. sub getMappedEntries {
  390. my $mapper = shift;
  391. my $ldiffiles = shift;
  392. my $errs = shift;
  393. my $callback = shift || \&cbaddent; # default - just add entry to @entries
  394. my @entries = ();
  395. my $context = shift || \@entries;
  396. my $error;
  397. if (!ref($ldiffiles)) {
  398. $ldiffiles = [ $ldiffiles ];
  399. }
  400. foreach my $ldiffile (@{$ldiffiles}) {
  401. if (!open(MYLDIF, "< $ldiffile")) {
  402. push @{$errs}, "error_opening_ldiftmpl", $ldiffile, $!;
  403. return 0;
  404. }
  405. my $in = new Mozilla::LDAP::LDIF(*MYLDIF);
  406. debug(1, "Processing $ldiffile ...\n");
  407. ENTRY: while (my $entry = Mozilla::LDAP::LDIF::readOneEntry($in)) {
  408. # first, fix the DN
  409. my $dn = $entry->getDN();
  410. my $origdn = $dn;
  411. while ( $dn =~ /%([\w_-]+)%/ ) {
  412. if (exists($mapper->{$1})) {
  413. $dn =~ s{%([\w_-]+)%}{$mapper->{$1}}ge;
  414. } else {
  415. push @{$errs}, 'error_mapping_token_ldiftmpl', $dn, $ldiffile, $1;
  416. $error = 1;
  417. last ENTRY;
  418. }
  419. }
  420. $entry->setDN($dn);
  421. # next, fix all of the values in all of the attributes
  422. foreach my $attr (keys %{$entry}) {
  423. my @newvalues = ();
  424. foreach my $value ($entry->getValues($attr)) {
  425. # Need to repeat to handle nested subst
  426. my $origvalue = $value;
  427. while ( $value =~ /%([\w_-]+)%/ ) {
  428. if (exists($mapper->{$1})) {
  429. $value =~ s{%([\w_-]+)%}{$mapper->{$1}}ge;
  430. } else {
  431. push @{$errs}, 'error_mapping_token_ldiftmpl', $dn, $ldiffile, $1;
  432. debug(1, "ERROR: \"$origvalue\" mapped to \"$value\".\n");
  433. $error = 1;
  434. last ENTRY;
  435. }
  436. }
  437. push @newvalues, $value;
  438. }
  439. $entry->setValues( $attr, @newvalues );
  440. }
  441. if (!&{$callback}($context, $entry, $errs)) {
  442. debug(1, "ERROR: There was an error processing entry ". $entry->getDN(). "\n");
  443. debug(1, "Cannot continue processing entries.\n");
  444. $error = 1;
  445. last ENTRY;
  446. }
  447. }
  448. close(MYLDIF);
  449. last if ($error); # do not process any more ldiffiles if an error occurred
  450. }
  451. return @entries;
  452. }
  453. # you should only use this function if you know for sure
  454. # that the suffix and backend do not already exist
  455. # use addSuffix instead
  456. sub newSuffixAndBackend {
  457. my $context = shift;
  458. my $suffix = shift;
  459. my $bename = shift;
  460. my $nsuffix = normalizeDN($suffix);
  461. my @errs;
  462. my $dn = "cn=$bename, cn=ldbm database, cn=plugins, cn=config";
  463. my $entry = new Mozilla::LDAP::Entry();
  464. $entry->setDN($dn);
  465. $entry->setValues('objectclass', 'top', 'extensibleObject', 'nsBackendInstance');
  466. $entry->setValues('cn', $bename);
  467. $entry->setValues('nsslapd-suffix', $nsuffix);
  468. $context->add($entry);
  469. my $rc = $context->getErrorCode();
  470. if ($rc) {
  471. return ('error_creating_suffix_backend', $suffix, $bename, $context->getErrorString());
  472. }
  473. $entry = new Mozilla::LDAP::Entry();
  474. $dn = "cn=\"$nsuffix\", cn=mapping tree, cn=config";
  475. $entry->setDN($dn);
  476. $entry->setValues('objectclass', 'top', 'extensibleObject', 'nsMappingTree');
  477. $entry->setValues('cn', "\"$nsuffix\"");
  478. $entry->setValues('nsslapd-state', 'backend');
  479. $entry->setValues('nsslapd-backend', $bename);
  480. $context->add($entry);
  481. $rc = $context->getErrorCode();
  482. if ($rc) {
  483. return ('error_creating_suffix', $suffix, $context->getErrorString());
  484. }
  485. return ();
  486. }
  487. sub findbecb {
  488. my $entry = shift;
  489. my $attrs = shift;
  490. return $entry->hasValue('objectclass', $attrs->[0], 1) &&
  491. $entry->hasValue('cn', $attrs->[1], 1);
  492. }
  493. sub findBackend {
  494. my $context = shift;
  495. my $bename = shift;
  496. my $ent;
  497. if (ref($context) eq 'Mozilla::LDAP::Conn') {
  498. $ent = $context->search("cn=ldbm database,cn=plugins,cn=config", "one",
  499. "(&(objectclass=nsBackendInstance)(cn=$bename)")
  500. } else {
  501. $ent = $context->search("cn=ldbm database,cn=plugins,cn=config", "one",
  502. \&findbecb, ['nsBackendInstance', $bename])
  503. }
  504. }
  505. sub findsuffixcb {
  506. my $entry = shift;
  507. my $attrs = shift;
  508. return $entry->hasValue('cn', $attrs->[0], 1) ||
  509. $entry->hasValue('cn', $attrs->[1], 1);
  510. }
  511. sub findSuffix {
  512. my $context = shift;
  513. my $suffix = shift;
  514. my $nsuffix = normalizeDN($suffix);
  515. my $ent;
  516. if (ref($context) eq 'Mozilla::LDAP::Conn') {
  517. $ent = $context->search("cn=mapping tree,cn=config", "one",
  518. "(|(cn=\"$suffix\")(cn=\"$nsuffix\"))");
  519. } else {
  520. $ent = $context->search("cn=mapping tree,cn=config", "one",
  521. \&findsuffixcb, ["\"$suffix\"", "\"$nsuffix\""])
  522. }
  523. }
  524. sub getUniqueBackendName {
  525. my $context = shift;
  526. my $bename = "backend";
  527. my $index = 0;
  528. my $ent = findBackend($context, ($bename . $index));
  529. while ($ent) {
  530. ++$index;
  531. $ent = findBackend($context, ($bename . $index));
  532. }
  533. return $bename.$index;
  534. }
  535. sub addSuffix {
  536. my $context = shift; # Conn
  537. my $suffix = shift;
  538. my $bename = shift; # optional
  539. my $ent;
  540. if ($bename && ($ent = findBackend($context, $bename))) {
  541. return ('backend_already_exists', $bename, $ent->getDN());
  542. }
  543. if ($ent = findSuffix($context, $suffix)) {
  544. return ('suffix_already_exists', $suffix, $ent->getDN());
  545. }
  546. if (!$bename) {
  547. $bename = getUniqueBackendName($context);
  548. }
  549. my @errs = newSuffixAndBackend($context, $suffix, $bename);
  550. return @errs;
  551. }
  552. # process map table
  553. # [map table sample]
  554. # fqdn = FullMachineName
  555. # hostname = `use Sys::Hostname; $returnvalue = hostname();`
  556. # ds_console_jar ="%normbrand%-ds-%ds_version%.jar"
  557. #
  558. # * If the right-hand value is in ` (backquote), the value is eval'ed by perl.
  559. # The output should be stored in $returnvalue to pass to the internal hash.
  560. # * If the right-hand value is in " (doublequote), the value is passed as is.
  561. # * If the right-hand value is not in any quote, the value should be found
  562. # in either of the setup inf file (static) or the install inf file (dynamic).
  563. # * Variables surrounded by @ (e.g., @admin_confdir@) are replaced with the
  564. # system path at the compile time.
  565. # * The right-hand value can contain variables surrounded by % (e.g., %asid%)
  566. # which refers the right-hand value (key) of this map file.
  567. # The %token% tokens are replaced in getMappedEntries
  568. sub process_maptbl
  569. {
  570. my ($mapper, $errs, @infdata) = @_;
  571. my @deferredkeys = ();
  572. if (defined($mapper->{""})) {
  573. $mapper = $mapper->{""}; # side effect of Inf with no sections
  574. }
  575. KEY: foreach my $key (keys %{$mapper})
  576. {
  577. my $value = $mapper->{$key};
  578. if ($value =~ /^\"/)
  579. {
  580. $value =~ tr/\"//d; # value is a regular double quoted string - remove quotes
  581. $mapper->{$key} = $value;
  582. }
  583. elsif ($value =~ /^\`/)
  584. {
  585. push @deferredkeys, $key; # process these last
  586. }
  587. else
  588. {
  589. # get the value from one of the Inf passed in
  590. # they $value could be pure Key or Key:"default_value"
  591. my ($key_value, $default_value) = split(/:/, $value, 2);
  592. my $infsection;
  593. foreach my $thisinf (@infdata)
  594. {
  595. foreach my $section0 (keys %{$thisinf})
  596. {
  597. $infsection = $thisinf->{$section0};
  598. next if (!ref($infsection));
  599. if (defined($infsection->{$key_value}))
  600. {
  601. $mapper->{$key} = $infsection->{$key_value};
  602. next KEY;
  603. }
  604. }
  605. }
  606. if (!defined($infsection->{$value}))
  607. {
  608. if ($default_value ne "")
  609. {
  610. $default_value =~ tr/\"//d; # default_value is a regular double quoted string - remove quotes
  611. $mapper->{$key} = $default_value;
  612. }
  613. else
  614. {
  615. push @{$errs}, 'no_mapvalue_for_key', $value, $key;
  616. return {};
  617. }
  618. }
  619. }
  620. }
  621. # we have to process the perl expressions to eval last, because those
  622. # expressions may use mappings defined elsewhere in the file, and we are not
  623. # guaranteed of the order in which hash keys are enumerated
  624. foreach my $key (@deferredkeys) {
  625. my $value = $mapper->{$key};
  626. $value =~ tr/\`//d; # value is a perl expression to eval
  627. my $returnvalue; # set in eval expression
  628. eval $value;
  629. $mapper->{$key} = $returnvalue; # perl expression sets $returnvalue
  630. }
  631. return $mapper;
  632. }
  633. # given a string, escape the characters in the string
  634. # so that it can be safely passed to the shell via
  635. # the system() call or `` backticks
  636. sub shellEscape {
  637. my $val = shift;
  638. # first, escape the double quotes and slashes
  639. $val =~ s/([\\"])/\\$1/g; # " font lock fun
  640. # next, escape the rest of the special chars
  641. my $special = '!$\' @#%^&*()|[\]{};:<>?/`';
  642. $val =~ s/([$special])/\\$1/g;
  643. return $val;
  644. }
  645. sub getHashedPassword {
  646. my $pwd = shift;
  647. my $alg = shift;
  648. if ($pwd =~ /\{\w+\}.+/) {
  649. return $pwd; # already hashed
  650. }
  651. my $cmd = "@bindir@/pwdhash";
  652. if ($alg) {
  653. $cmd .= " -s $alg";
  654. }
  655. $cmd .= " " . shellEscape($pwd);
  656. my $hashedpwd = `$cmd`;
  657. chomp($hashedpwd);
  658. return $hashedpwd;
  659. }
  660. # this creates an Inf suitable for passing to createDSInstance
  661. # except that it has a bogus suffix
  662. sub createInfFromConfig {
  663. my $configdir = shift;
  664. my $inst = shift;
  665. my $errs = shift;
  666. my $fname = "$configdir/dse.ldif";
  667. my $id;
  668. ($id = $inst) =~ s/^slapd-//;
  669. if (! -f $fname || ! -r $fname) {
  670. push @{$errs}, "error_opening_dseldif", $fname, $!;
  671. return 0;
  672. }
  673. my $conn = new FileConn($fname, 1);
  674. if (!$conn) {
  675. push @{$errs}, "error_opening_dseldif", $fname, $!;
  676. return 0;
  677. }
  678. my $ent = $conn->search("cn=config", "base", "(objectclass=*)");
  679. if (!$ent) {
  680. push @{$errs}, "error_opening_dseldif", $fname, $!;
  681. $conn->close();
  682. return 0;
  683. }
  684. my ($outfh, $inffile) = tempfile(SUFFIX => '.inf');
  685. if (!$outfh || !$inffile) {
  686. push @{$errs}, "error_opening_tempinf", $fname, $!;
  687. if ($outfh) {
  688. close $outfh;
  689. }
  690. $conn->close();
  691. return 0;
  692. }
  693. print $outfh "[General]\n";
  694. print $outfh "FullMachineName = ", $ent->getValues('nsslapd-localhost'), "\n";
  695. print $outfh "SuiteSpotUserID = ", $ent->getValues('nsslapd-localuser'), "\n";
  696. print $outfh "[slapd]\n";
  697. print $outfh "RootDN = ", $ent->getValues('nsslapd-rootdn'), "\n";
  698. print $outfh "RootDNPwd = ", $ent->getValues('nsslapd-rootpw'), "\n";
  699. print $outfh "ServerPort = ", $ent->getValues('nsslapd-port'), "\n";
  700. print $outfh "ServerIdentifier = $id\n";
  701. my $suffix;
  702. $ent = $conn->search("cn=ldbm database,cn=plugins,cn=config",
  703. "one", "(objectclass=*)");
  704. if (!$ent) {
  705. push @{$errs}, "error_opening_dseldif", $fname, $!;
  706. close $outfh;
  707. $conn->close();
  708. return 0;
  709. }
  710. # use the userRoot suffix if available
  711. while ($ent) {
  712. $suffix = $ent->getValues('nsslapd-suffix');
  713. last if ($ent->hasValue('cn', 'userRoot', 1));
  714. $ent = $conn->nextEntry();
  715. }
  716. # we also need the instance dir
  717. $ent = $conn->search("cn=config", "base", "(objectclass=*)");
  718. if (!$ent) {
  719. push @{$errs}, "error_opening_dseldif", $fname, $!;
  720. close $outfh;
  721. $conn->close();
  722. return 0;
  723. }
  724. my $inst_dir = $ent->getValue('nsslapd-instancedir');
  725. $conn->close();
  726. if ($inst_dir) {
  727. print $outfh "inst_dir = $inst_dir\n";
  728. }
  729. print $outfh "Suffix = $suffix\n";
  730. close $outfh;
  731. my $inf = new Inf($inffile);
  732. return $inf;
  733. }
  734. # like File::Path mkpath, except we can set the owner and perm
  735. # of each new path and parent path created
  736. sub makePaths {
  737. my ($path, $mode, $user, $group) = @_;
  738. my $uid = getpwnam $user;
  739. my $gid = -1; # default to leave it alone
  740. my $mode_string = "";
  741. if ($group) {
  742. $gid = getgrnam $group;
  743. }
  744. my @dirnames = ($path);
  745. my $parent = $path;
  746. for ($parent = dirname($parent);
  747. $parent and ($parent ne "/");
  748. $parent = dirname($parent)) {
  749. unshift @dirnames, $parent;
  750. }
  751. for (@dirnames) {
  752. next if (-d $_);
  753. $! = 0; # clear
  754. mkdir $_, $mode;
  755. if ($!) {
  756. return ('error_creating_directory', $_, $!);
  757. }
  758. chown $uid, $gid, $_;
  759. if ($!) {
  760. return ('error_chowning_directory', $_, $!);
  761. }
  762. chmod $mode, $_;
  763. $mode_string = sprintf "%lo", $mode;
  764. debug(1, "makePaths: created directory $_ mode $mode_string user $user group $group\n");
  765. debug(2, "\t" . `ls -ld $_`);
  766. }
  767. return ();
  768. }
  769. # remove_tree($centry, $key, $instname, [$isparent, [$dontremove]])
  770. # $centry: entry to look for the path to be removed
  771. # $key: key to look for the path in the entry
  772. # $instname: instance name "slapd-<ID>" to check the path
  773. # $isparent: specify 1 to remove from the parent dir
  774. # $dontremove: pattern not to be removed (e.g., ".db$")
  775. sub remove_tree
  776. {
  777. my $centry = shift;
  778. my $key = shift;
  779. my $instname = shift;
  780. my $isparent = shift;
  781. my $dontremove = shift;
  782. my @errs = (); # a list of array refs - each array ref is suitable for passing to Resource::getText
  783. foreach my $path ( @{$centry->{$key}} )
  784. {
  785. my $rmdir = "";
  786. my $rc = 0;
  787. if ( 1 == $isparent )
  788. {
  789. $rmdir = dirname($path);
  790. }
  791. else
  792. {
  793. $rmdir = $path;
  794. }
  795. if ( -d $rmdir && $rmdir =~ /$instname/ )
  796. {
  797. if ( "" eq "$dontremove" )
  798. {
  799. $rc = rmtree($rmdir);
  800. if ( 0 == $rc )
  801. {
  802. push @errs, [ 'error_removing_path', $rmdir, $! ];
  803. debug(1, "Warning: $rmdir was not removed. Error: $!\n");
  804. }
  805. }
  806. else
  807. {
  808. # Skip the dontremove files
  809. $rc = opendir(DIR, $rmdir);
  810. if ($rc)
  811. {
  812. while (defined(my $file = readdir(DIR)))
  813. {
  814. next if ( "$file" =~ /$dontremove/ );
  815. next if ( "$file" eq "." );
  816. next if ( "$file" eq ".." );
  817. my $rmfile = $rmdir . "/" . $file;
  818. my $rc0 = rmtree($rmfile);
  819. if ( 0 == $rc0 )
  820. {
  821. push @errs, [ 'error_removing_path', $rmfile, $! ];
  822. debug(1, "Warning: $rmfile was not removed. Error: $!\n");
  823. }
  824. }
  825. closedir(DIR);
  826. }
  827. my $newrmdir = $rmdir . ".removed";
  828. my $rc1 = 1;
  829. if ( -d $newrmdir )
  830. {
  831. $rc1 = rmtree($newrmdir);
  832. if ( 0 == $rc1 )
  833. {
  834. push @errs, [ 'error_removing_path', $newrmdir, $! ];
  835. debug(1, "Warning: $newrmdir was not removed. Error: $!\n");
  836. }
  837. }
  838. if ( 0 < $rc1 )
  839. {
  840. rename($rmdir, $newrmdir);
  841. }
  842. }
  843. }
  844. }
  845. return @errs; # a list of array refs - if (!@errs) then success
  846. }
  847. sub remove_pidfile
  848. {
  849. my ($type, $instdir, $instname) = @_;
  850. my $serv_id;
  851. my $run_dir;
  852. my $product_name;
  853. my $pidfile;
  854. # Get the serv_id from the start-slapd script.
  855. unless(open(INFILE,"$instdir/start-slapd")) {
  856. print("Cannot open start-slapd file for reading "); return 0;
  857. }
  858. while(<INFILE>) {
  859. if (/start-dirsrv /g) {
  860. my @servline=split(/start-dirsrv /, );
  861. @servline=split(/\s+/, $servline[1]);
  862. $serv_id=$servline[0];
  863. }
  864. }
  865. close(INFILE);
  866. # Get the run_dir and product_name from the instance initconfig script.
  867. unless(open(INFILE,"@initconfigdir@/@package_name@-$serv_id")) {
  868. print("Couldn't open @initconfigdir@/@package_name@-$serv_id "); return 0;
  869. }
  870. while(<INFILE>) {
  871. if (/RUN_DIR=/g) {
  872. my @rundir_line=split(/RUN_DIR=+/, );
  873. @rundir_line=split(/;/, $rundir_line[1]);
  874. $run_dir = $rundir_line[0];
  875. chop($run_dir);
  876. } elsif (/PRODUCT_NAME=/g) {
  877. my @product_line=split(/PRODUCT_NAME=+/, );
  878. @product_line=split(/;/, $product_line[1]);
  879. $product_name = $product_line[0];
  880. chop($product_name);
  881. }
  882. }
  883. close(INFILE);
  884. # Construct the pidfile name as follows:
  885. # PIDFILE=$RUN_DIR/$PRODUCT_NAME-$SERV_ID.pid
  886. # STARTPIDFILE=$RUN_DIR/$PRODUCT_NAME-$SERV_ID.startpid
  887. if ($type eq "PIDFILE") {
  888. $pidfile = $run_dir . "/" . $product_name . "-" . $serv_id . ".pid";
  889. } elsif ($type eq "STARTPIDFILE") {
  890. $pidfile = $run_dir . "/" . $product_name . "-" . $serv_id . ".startpid";
  891. }
  892. if ( -e $pidfile && $pidfile =~ /$instname/ )
  893. {
  894. unlink($pidfile);
  895. }
  896. }
  897. 1;
  898. # emacs settings
  899. # Local Variables:
  900. # mode:perl
  901. # indent-tabs-mode: nil
  902. # tab-width: 4
  903. # End: