DSUtil.pm.in 35 KB

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