1
0

FileConn.pm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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. # FileConn is a subclass of Mozilla::LDAP::Conn. This class does
  39. # not use LDAP. Instead, it operates on a given LDAP file, allowing
  40. # you to search, add, modify, and delete entries in the file.
  41. #
  42. package FileConn;
  43. use Mozilla::LDAP::Conn;
  44. use Mozilla::LDAP::API qw(:constant ldap_explode_dn ldap_err2string); # Direct access to C API
  45. use Mozilla::LDAP::Utils qw(normalizeDN);
  46. use Mozilla::LDAP::LDIF;
  47. use Carp;
  48. require Exporter;
  49. @ISA = qw(Exporter Mozilla::LDAP::Conn);
  50. @EXPORT = qw();
  51. @EXPORT_OK = qw();
  52. sub new {
  53. my $class = shift;
  54. my $filename = shift;
  55. my $readonly = shift;
  56. my @namingContexts = @_;
  57. my $self = {};
  58. $self = bless $self, $class;
  59. $self->{readonly} = $readonly;
  60. for (@namingContexts) {
  61. $self->setNamingContext($_);
  62. }
  63. $self->setNamingContext(""); # root DSE
  64. $self->read($filename);
  65. return $self;
  66. }
  67. sub getParentDN {
  68. my $dn = shift;
  69. my @rdns = ldap_explode_dn($dn, 0);
  70. shift @rdns;
  71. return join(',', @rdns);
  72. }
  73. sub read {
  74. my $self = shift;
  75. my $filename = shift;
  76. if ($filename) {
  77. $self->{filename} = $filename;
  78. } else {
  79. $filename = $self->{filename};
  80. }
  81. if (!$self->{filename}) {
  82. return;
  83. }
  84. open( MYLDIF, "$filename" ) || confess "Can't open $filename: $!";
  85. my $in = new Mozilla::LDAP::LDIF(*MYLDIF);
  86. $self->{reading} = 1;
  87. while ($ent = readOneEntry $in) {
  88. if (!$self->add($ent)) {
  89. confess "Error: could not add entry ", $ent->getDN(), ":", $self->getErrorString();
  90. }
  91. }
  92. delete $self->{reading};
  93. close( MYLDIF );
  94. }
  95. sub setNamingContext {
  96. my $self = shift;
  97. my $nc = shift;
  98. my $ndn = normalizeDN($nc);
  99. $self->{namingContexts}->{$ndn} = $ndn;
  100. }
  101. sub isNamingContext {
  102. my $self = shift;
  103. my $ndn = shift;
  104. return exists($self->{namingContexts}->{$ndn});
  105. }
  106. # return all nodes below the given node
  107. sub iterate {
  108. my $self = shift;
  109. my $dn = shift;
  110. my $scope = shift;
  111. my $callback = shift;
  112. my $context = shift;
  113. my $suppress = shift;
  114. my $ndn = normalizeDN($dn);
  115. my $children;
  116. if (exists($self->{$ndn}) and exists($self->{$ndn}->{children})) {
  117. $children = $self->{$ndn}->{children};
  118. }
  119. if (($scope != LDAP_SCOPE_ONELEVEL) && exists($self->{$ndn}) &&
  120. exists($self->{$ndn}->{data}) && $self->{$ndn}->{data} && !$suppress) {
  121. &{$callback}($self->{$ndn}->{data}, $context);
  122. }
  123. if ($scope == LDAP_SCOPE_BASE) {
  124. return;
  125. }
  126. for my $node (@{$children}) {
  127. &{$callback}($node->{data}, $context);
  128. }
  129. if ($scope == LDAP_SCOPE_SUBTREE) {
  130. for my $node (@{$children}) {
  131. $self->iterate($node->{data}->getDN(), $scope, $callback, $context, 1);
  132. }
  133. }
  134. }
  135. sub writecb {
  136. my $entry = shift;
  137. my $fh = shift;
  138. if (! $entry->getDN()) { # rootDSE requires special hack around perldap bug
  139. my $ary = $entry->getLDIFrecords();
  140. shift @$ary; # remove "dn"
  141. shift @$ary; # remove the empty dn value
  142. print $fh "dn:\n";
  143. print $fh (Mozilla::LDAP::LDIF::pack_LDIF (78, $ary), "\n");
  144. } else {
  145. Mozilla::LDAP::LDIF::put_LDIF($fh, 78, $entry);
  146. }
  147. }
  148. sub write {
  149. my $self = shift;
  150. my $filename = shift;
  151. if ($filename) {
  152. $self->{filename} = $filename;
  153. } else {
  154. $filename = $self->{filename};
  155. }
  156. if (!$self->{filename} or $self->{readonly} or $self->{reading}) {
  157. return;
  158. }
  159. open( MYLDIF, ">$filename" ) || confess "Can't write $filename: $!";
  160. $self->iterate("", LDAP_SCOPE_SUBTREE, \&writecb, \*MYLDIF);
  161. for (keys %{$self->{namingContexts}}) {
  162. next if (!$_); # skip "" - we already did that
  163. $self->iterate($_, LDAP_SCOPE_SUBTREE, \&writecb, \*MYLDIF);
  164. }
  165. close( MYLDIF );
  166. }
  167. sub setErrorCode {
  168. my $self = shift;
  169. $self->{lastErrorCode} = shift;
  170. }
  171. sub getErrorCode {
  172. my $self = shift;
  173. return $self->{lastErrorCode};
  174. }
  175. sub getErrorString {
  176. my $self = shift;
  177. return ($self->{lastErrorCode} ? ldap_err2string($self->{lastErrorCode}) : LDAP_SUCCESS);
  178. }
  179. #############################################################################
  180. # Print the last error code...
  181. #
  182. sub printError
  183. {
  184. my ($self, $str) = @_;
  185. $str = "LDAP error:" unless defined($str);
  186. print "$str ", $self->getErrorString(), "\n";
  187. }
  188. sub DESTROY {
  189. my $self = shift;
  190. $self->close();
  191. }
  192. sub close {
  193. my $self = shift;
  194. return if ($self->{readonly});
  195. $self->write();
  196. }
  197. sub printcb {
  198. my $entry = shift;
  199. print $entry->getDN(), "\n";
  200. }
  201. sub print {
  202. my $self = shift;
  203. my $dn = shift;
  204. my $scope = shift;
  205. $self->iterate($dn, $scope, \&printcb);
  206. }
  207. # for each entry, call the user provided filter callback
  208. # with the entry and the user provided filter context
  209. # if the filtercb returns true, add the entry to the
  210. # list of entries to return
  211. sub searchcb {
  212. my $entry = shift;
  213. my $context = shift;
  214. my $self = $context->[0];
  215. my $filtercb = $context->[1];
  216. my $filtercontext = $context->[2];
  217. if (&{$filtercb}($entry, $filtercontext)) {
  218. push @{$self->{entries}}, $entry;
  219. }
  220. }
  221. sub matchall {
  222. return 1;
  223. }
  224. sub matchAttrVal {
  225. my $entry = shift;
  226. my $context = shift;
  227. my $attr = $context->[0];
  228. my $val = $context->[1];
  229. if ($val eq "*") {
  230. return $entry->exists($attr);
  231. }
  232. return $entry->hasValue($attr, $val, 1);
  233. }
  234. my $attrpat = '[-;.:\w]*[-;\w]';
  235. # given a string filter, figure out which subroutine to
  236. # use to match
  237. sub filterToMatchSub {
  238. my $self = shift;
  239. my ($basedn, $scope, $filter, $attrsonly, @rest) = @_;
  240. my ($matchsub, $context);
  241. # do some filter processing
  242. if (!$filter or ($filter eq "(objectclass=*)") or
  243. ($filter eq "objectclass=*")) {
  244. $matchsub = \&matchall;
  245. } elsif ($filter =~ /^\(($attrpat)=(.+)\)$/o) {
  246. push @{$context}, $1, $2;
  247. $matchsub = \&matchAttrVal;
  248. # } elsif ($filter =~ /^\(\|\(($attrpat)=(.+)\)\(($attrpat)=(.+)\)\)$/o) {
  249. # $attr = $1;
  250. # $val = $2;
  251. # $attr1 = $1;
  252. # $val1 = $2;
  253. # $isand = 0;
  254. # } elsif ($filter =~ /^\(\&\(($attrpat)=(.+)\)\(($attrpat)=(.+)\)\)$/o) {
  255. # $attr = $1;
  256. # $val = $2;
  257. # $attr1 = $1;
  258. # $val1 = $2;
  259. # $isand = 1;
  260. # } elsif ($filter =~ /^\(\|\(($attrpat)=(.+)\)\(($attrpat)=(.+)\)\)$/o) {) {
  261. # # "(&(objectclass=nsBackendInstance)(|(nsslapd-suffix=$suffix)(nsslapd-suffix=$nsuffix)))");
  262. }
  263. $self->iterate($basedn, $scope, \&searchcb, [$self, $matchsub, $context]);
  264. }
  265. # simple searches only
  266. sub search {
  267. my $self = shift;
  268. my ($basedn, $scope, $filter, $attrsonly, @rest) = @_;
  269. my $attrs;
  270. if (ref($rest[0]) eq "ARRAY") {
  271. $attrs = $rest[0];
  272. } elsif (scalar(@rest) > 0) {
  273. $attrs = \@rest;
  274. }
  275. $scope = Mozilla::LDAP::Utils::str2Scope($scope);
  276. $self->{entries} = [];
  277. my $ndn = normalizeDN($basedn);
  278. if (!exists($self->{$ndn}) or !exists($self->{$ndn}->{data})) {
  279. $self->setErrorCode(LDAP_NO_SUCH_OBJECT);
  280. return undef;
  281. }
  282. $self->setErrorCode(0);
  283. if (ref($filter) eq 'CODE') {
  284. $self->iterate($basedn, $scope, \&searchcb, [$self, $filter, $attrsonly]);
  285. } else {
  286. $self->filterToMatchSub($basedn, $scope, $filter, $attrsonly);
  287. }
  288. return $self->nextEntry();
  289. }
  290. sub cloneEntry {
  291. my $src = shift;
  292. if (!$src) {
  293. return undef;
  294. }
  295. my $dest = new Mozilla::LDAP::Entry();
  296. $dest->setDN($src->getDN());
  297. for (keys %{$src}) {
  298. if (ref($src->{$_})) {
  299. my @copyary = @{$src->{$_}};
  300. $dest->{$_} = [ @copyary ]; # make a deep copy
  301. } else {
  302. $dest->{$_} = $src->{$_};
  303. }
  304. }
  305. return $dest;
  306. }
  307. # have to return a copy of the entry - disallow inplace updates
  308. sub nextEntry {
  309. my $self = shift;
  310. my $ent = shift @{$self->{entries}};
  311. return cloneEntry($ent);
  312. }
  313. sub add {
  314. my $self = shift;
  315. my $entry = shift;
  316. my $dn = $entry->getDN();
  317. my $ndn = normalizeDN($dn);
  318. my $parentdn = getParentDN($dn);
  319. my $nparentdn = normalizeDN($parentdn);
  320. $self->setErrorCode(0);
  321. # special case of naming context - has no parent
  322. if ($self->isNamingContext($ndn) and
  323. !exists($self->{$ndn}->{data})) {
  324. $self->{$ndn}->{data} = $entry;
  325. $self->write();
  326. return 1;
  327. }
  328. if (exists($self->{$ndn})) {
  329. $self->setErrorCode(LDAP_ALREADY_EXISTS);
  330. return 0;
  331. }
  332. if ($ndn && $nparentdn && !exists($self->{$nparentdn})) {
  333. $self->setErrorCode(LDAP_NO_SUCH_OBJECT);
  334. return 0;
  335. }
  336. # each hash entry has two keys
  337. # data is the actual Entry
  338. # children is the array ref of the one level children of this dn
  339. $self->{$ndn}->{data} = $entry;
  340. # don't add parent to list of children
  341. if ($nparentdn ne $ndn) {
  342. push @{$self->{$nparentdn}->{children}}, $self->{$ndn};
  343. }
  344. return 1;
  345. }
  346. sub update {
  347. my $self = shift;
  348. my $entry = shift;
  349. my $dn = $entry->getDN();
  350. my $ndn = normalizeDN($dn);
  351. confess "Attempt to modify read only $self->{filename} entry $dn" if ($self->{readonly});
  352. $self->setErrorCode(0);
  353. if (!exists($self->{$ndn})) {
  354. $self->setErrorCode(LDAP_NO_SUCH_OBJECT);
  355. return 0;
  356. }
  357. # The cloned entry will not contain the deleted attrs - the cloning
  358. # process omits the deleted attrs via the Entry FETCH, FIRSTKEY, and NEXTKEY
  359. # methods
  360. $self->{$ndn}->{data} = cloneEntry($entry);
  361. $self->write();
  362. return 1;
  363. }
  364. sub delete {
  365. my $self = shift;
  366. my $dn = shift;
  367. confess "Attempt to modify read only $self->{filename} entry $dn" if ($self->{readonly});
  368. if (ref($dn)) {
  369. $dn = $dn->getDN(); # an Entry
  370. }
  371. my $ndn = normalizeDN($dn);
  372. $self->setErrorCode(0);
  373. if (!exists($self->{$ndn})) {
  374. $self->setErrorCode(LDAP_NO_SUCH_OBJECT);
  375. return 0;
  376. }
  377. if (@{$self->{$ndn}->{children}}) {
  378. $self->setErrorCode(LDAP_NOT_ALLOWED_ON_NONLEAF);
  379. return 0;
  380. }
  381. # delete the data associated with this node
  382. delete $self->{$ndn}->{data};
  383. delete $self->{$ndn}->{children};
  384. my $parentdn = getParentDN($dn);
  385. my $nparentdn = normalizeDN($parentdn);
  386. # delete this node from its parent
  387. if ($ndn ne $nparentdn) {
  388. for (my $ii = 0; $ii < @{$self->{$nparentdn}->{children}}; ++$ii) {
  389. # find matching hash ref in parent's child list
  390. if ($self->{$nparentdn}->{children}->[$ii] eq $self->{$ndn}) {
  391. # remove that element from the array
  392. splice @{$self->{$nparentdn}->{children}}, $ii, 1;
  393. # done - should only ever be one matching child
  394. last;
  395. }
  396. }
  397. }
  398. # delete this node
  399. delete $self->{$ndn};
  400. $self->write();
  401. return 1;
  402. }
  403. 1;