File Coverage

blib/lib/Kolab/LDAP.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Kolab::LDAP;
2              
3             ##
4             ## Copyright (c) 2003 Code Fusion cc
5             ##
6             ## Writen by Stuart Bingė
7             ##
8             ## This program is free software; you can redistribute it and/or
9             ## modify it under the terms of the GNU General Public License as
10             ## published by the Free Software Foundation; either version 2, or
11             ## (at your option) any later version.
12             ##
13             ## This program is distributed in the hope that it will be useful,
14             ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15             ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16             ## General Public License for more details.
17             ##
18             ## You can view the GNU General Public License, online, at the GNU
19             ## Project's homepage; see .
20             ##
21              
22 1     1   44319 use 5.008;
  1         4  
  1         60  
23 1     1   6 use strict;
  1         2  
  1         38  
24 1     1   5 use warnings;
  1         6  
  1         33  
25 1     1   492 use Net::LDAP;
  0            
  0            
26             use DB_File;
27             use Kolab;
28             use Kolab::Util;
29             use Kolab::Cyrus;
30             use Kolab::DirServ;
31             use vars qw(%uid_db %gyard_db %newuid_db %gyard_ts_db);
32              
33             require Exporter;
34              
35             our @ISA = qw(Exporter);
36              
37             our %EXPORT_TAGS = (
38             'all' => [ qw(
39             &startup
40             &shutdown
41             &create
42             &destroy
43             &ensureAsync
44             &isObject
45             &isDeleted
46             &createObject
47             &deleteObject
48             &sync
49             ) ]
50             );
51              
52             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
53              
54             our @EXPORT = qw(
55              
56             );
57              
58             our $VERSION = '1.02';
59              
60             sub startup
61             {
62             Kolab::log('L', 'Starting up');
63              
64             Kolab::log('L', 'Opening mailbox uid cache DB');
65              
66             if (!dbmopen(%uid_db, $Kolab::config{'prefix'} . '/var/kolab/mailbox-uidcache.db', 0666)) {
67             Kolab::log('L', 'Unable to open mailbox uid cache DB', KOLAB_ERROR);
68             exit(1);
69             }
70              
71             Kolab::log('L', 'Opening graveyard uid/timestamp cache DB');
72              
73             if (!dbmopen(%gyard_db, $Kolab::config{'prefix'} . '/var/kolab/graveyard-uidcache.db', 0666)) {
74             Kolab::log('L', 'Unable to open graveyard uid cache DB', KOLAB_ERROR);
75             exit(1);
76             }
77              
78             if (!dbmopen(%gyard_ts_db, $Kolab::config{'prefix'} . '/var/kolab/graveyard-tscache.db', 0666)) {
79             Kolab::log('L', 'Unable to open graveyard timestamp cache DB', KOLAB_ERROR);
80             exit(1);
81             }
82             }
83              
84             sub shutdown
85             {
86             Kolab::log('L', 'Shutting down');
87              
88             dbmclose(%uid_db);
89             dbmclose(%gyard_db);
90             }
91              
92             sub create
93             {
94             my $ip = shift;
95             my $pt = shift;
96             my $dn = shift;
97             my $pw = shift;
98             my $as = shift || 0;
99              
100             Kolab::log('L', "Connecting to LDAP server `$ip:$pt'");
101              
102             my $ldap = Net::LDAP->new(
103             $ip,
104             port => $pt,
105             version => 3,
106             timeout => 5,
107             async => $as,
108             );
109             if (!$ldap) {
110             Kolab::log('L', "Unable to connect to LDAP server `$ip:$pt'", KOLAB_ERROR);
111             if ($as) { return 0; } else { exit(1); }
112             }
113              
114             Kolab::log('L', "Binding to `$dn'");
115             my $ldapmesg = $ldap->bind(
116             $dn,
117             password => $pw
118             );
119             if ($ldapmesg->code) {
120             Kolab::log('L', "Unable to bind to `$dn', LDAP Error = `" . $ldapmesg->error . "'", KOLAB_ERROR);
121             if ($as) { return 0; } else { exit(1); }
122             }
123              
124             return $ldap;
125             }
126              
127             sub destroy
128             {
129             my $ldap = shift;
130              
131             if (defined($ldap) && $ldap->isa('Net::LDAP')) {
132             $ldap->abandon;
133             $ldap->unbind;
134             $ldap->disconnect;
135             }
136             }
137              
138             sub ensureAsync
139             {
140             my $ldap = shift || 0;
141              
142             if ($ldap && !$ldap->async) {
143             Kolab::log('L', 'LDAP operations are not asynchronous', KOLAB_ERROR);
144             exit(1);
145             }
146              
147             Kolab::log('L', 'LDAP operations are asynchronous', KOLAB_DEBUG);
148             }
149              
150             sub isObject
151             {
152             my $object = shift;
153             my $class = shift;
154              
155             my $classes = $object->get_value('objectClass', asref => 1);
156             return 0 if !defined($classes);
157             foreach my $oc (@$classes) {
158             if ($oc =~ /$class/i) {
159             return 1;
160             }
161             }
162             return 0;
163             }
164              
165             sub isDeleted
166             {
167             my $object = shift;
168             my $p = shift || 'user';
169             my $del = $object->get_value($Kolab::config{$p . '_field_deleted'}) || '';
170             return ($del =~ /true/i);
171             }
172              
173             sub createObject
174             {
175             my $ldap = shift;
176             my $cyrus = shift;
177             my $object = shift;
178             my $sync = shift || 0;
179             my $p = shift || 'user';
180             my $doacls = shift || 0;
181             my $objuidfield = shift || ($p eq 'user' ? 'mail' : ($p eq 'sf' ? 'cn' : ''));
182              
183             Kolab::log('L', "Kolab::LDAP::createObject() called with obj uid field `$objuidfield' for obj type `$p'", KOLAB_DEBUG);
184              
185             my $uid = trim($object->get_value($objuidfield)) || 0;
186             if (!$uid) {
187             Kolab::log('L', "Kolab::LDAP::createObject() called with null id attribute `$objuidfield', returning", KOLAB_DEBUG);
188             return;
189             }
190              
191             Kolab::log('L', "Synchronising object `$uid'", KOLAB_DEBUG);
192              
193             my $guid = $object->get_value($Kolab::config{$p . '_field_guid'});
194             Kolab::log('L', "GUID attribute `" . $Kolab::config{$p . '_field_guid'} . "' is `$guid'", KOLAB_DEBUG);
195             my $olduid = $uid_db{$guid} || '';
196             if ($olduid) {
197             # We have records of the object
198             $newuid_db{$guid} = $olduid if ($sync);
199             if ($olduid ne $uid) {
200             # The mailbox changed; bitch
201             Kolab::log('L', "Object `$uid' already exists as `$olduid'; refusing to create", KOLAB_WARN);
202             }
203             # Nothing changed; nothing to do
204             #Kolab::DirServ::genericRequest($object, "modify alias");
205             } else {
206             # No official records - check the graveyard
207             my $oldgyarduid = $gyard_db{$guid} || '';
208             if ($oldgyarduid) {
209             # The object needs to be resurrected!
210             if ($oldgyarduid ne $uid) {
211             Kolab::log('L', "Resurrected object `$uid' already exists as `$oldgyarduid'; refusing to create", KOLAB_WARN);
212             } else {
213             Kolab::log('L', "Object `$uid' has been resurrected", KOLAB_DEBUG);
214             }
215              
216             # Remove the object from the graveyard
217             if ($sync) { $newuid_db{$guid} = $oldgyarduid; } else { $uid_db{$guid} = $oldgyarduid; }
218             delete $gyard_db{$guid};
219             delete $gyard_ts_db{$guid};
220             } else {
221             Kolab::log('L', "Creating user `$uid' corresponding to GUID `$guid'", KOLAB_DEBUG);
222             # We have a object that we have no previous record of, so create everything
223             if ($sync) { $newuid_db{$guid} = $uid; } else { $uid_db{$guid} = $uid; }
224             Kolab::Cyrus::createMailbox($cyrus, $uid, ($p eq 'sf' ? 1 : 0));
225              
226             Kolab::DirServ::genericRequest($object, "new alias") if $p eq 'user';
227             }
228             }
229              
230             if ($doacls) {
231             my $acls = $object->get_value('acl', 'asref' => 1);
232             Kolab::Cyrus::setACL($cyrus, $uid, ($p eq 'sf' ? 1 : 0), $acls);
233             }
234              
235             my $quota = $object->get_value($Kolab::config{$p . '_field_quota'});
236             Kolab::Cyrus::setQuota($cyrus, $uid, $quota);
237             }
238              
239             sub deleteObject
240             {
241             # This should only ever be called if the object is specifically flagged for
242             # deletion, as we nuke the mailbox
243             #
244             # The graveyard code will handle the case of an object `going missing'.
245              
246             my $ldap = shift;
247             my $cyrus = shift;
248             my $object = shift;
249             my $remfromldap = shift || 0;
250             my $p = shift || 'user';
251              
252             if ($remfromldap) {
253             my $dn = $object->dn;
254             Kolab::log('L', "Removing DN `$dn'");
255             my $mesg = $ldap->delete($dn);
256             if ($mesg->code) {
257             Kolab::log('L', "Unable to remove DN `$dn'", KOLAB_WARN);
258             }
259             }
260              
261             my $guid = $object->get_value($Kolab::config{$p . '_field_guid'});
262             my $uid = $uid_db{$guid} || 0;
263             if (!$uid) {
264             Kolab::Util::log('L', 'Deleted object not found in mboxcache, returning', KOLAB_DEBUG);
265             return;
266             }
267              
268             Kolab::DirServ::genericRequest($object, "remove alias") if $p eq 'user';
269              
270             Kolab::Cyrus::deleteMailbox($cyrus, $uid, ($p eq 'sf' ? 1 : 0));
271             delete $uid_db{$guid};
272             return;
273             }
274              
275             sub sync
276             {
277             Kolab::log('L', 'Synchronising');
278              
279             my $cyrus = Kolab::Cyrus::create;
280             %newuid_db = ();
281              
282             syncBasic($cyrus, 'user', '(mail=*)', 0);
283             syncBasic($cyrus, 'sf', '', 1);
284              
285             # Check that all mailboxes correspond to LDAP objects
286             Kolab::log('L', 'Synchronising mailboxes');
287              
288             my @mailboxes = $cyrus->list('*');
289             my %objects;
290             my $mailbox;
291             foreach $mailbox (@mailboxes) {
292             my $u = ${@{$mailbox}}[0];
293             $u =~ /user[\/\.]([^\/]*)\/?.*/;
294             $objects{$1} = 1 if ($1);
295             }
296              
297             my $guid;
298             foreach $guid (keys %newuid_db) {
299             delete $objects{$newuid_db{$guid}} if (exists $objects{$newuid_db{$guid}});
300             }
301              
302             # Any mailboxes left should be sent to the graveyard; these are mailboxes
303             # without a corresponding LDAP object, yet we were never informed of their
304             # deletion, i.e. either we missed the deletion notification or there was
305             # an error when iterating through the objects (Lost connection, invalid DNs)
306             foreach $guid (keys %uid_db) {
307             if (exists $objects{$uid_db{$guid}}) {
308             $gyard_db{$guid} = $uid_db{$guid};
309             $gyard_ts_db{$guid} = time;
310             }
311             }
312              
313             my $now = time;
314             my $period = $Kolab::config{'gyard_deletion_period'} * 60;
315             Kolab::log('L', 'Gravekeeping (period = ' . $Kolab::config{'gyard_deletion_period'} . ' minutes)');
316             foreach $guid (keys %gyard_ts_db) {
317             if ($now - $gyard_ts_db{$guid} > $period) {
318             Kolab::log('L', "Gravekeeper deleting mailbox `" . $gyard_db{$guid} . "'");
319             Kolab::Cyrus::deleteMailbox($cyrus, $gyard_db{$guid}, 0);
320             delete $gyard_ts_db{$guid};
321             delete $gyard_db{$guid};
322             }
323             }
324              
325             %uid_db = %newuid_db;
326              
327             Kolab::log('L', 'Finished synchronisation');
328             }
329              
330             sub syncBasic
331             {
332             my $cyrus = shift;
333             my $p = shift || 'user';
334             my $add = shift || ($p eq 'user' ? '(mail=*)' : '');
335             my $doacls = shift || 0;
336              
337             Kolab::log('L', "Synchronising `$p' objects");
338              
339             my $ldap = &create(
340             $Kolab::config{$p . '_ldap_ip'},
341             $Kolab::config{$p . '_ldap_port'},
342             $Kolab::config{$p . '_bind_dn'},
343             $Kolab::config{$p . '_bind_pw'}
344             );
345              
346             my $ldapmesg;
347             my $ldapobject;
348              
349             my @dnlist = split(/;/, $Kolab::config{$p . '_dn_list'});
350             my $dn;
351              
352             foreach $dn (@dnlist) {
353             Kolab::log('L', "Synchronising `$p' DN `$dn'");
354              
355             # First of all, remove any objects explicitly marked for deletion
356             $ldapmesg = $ldap->search(
357             base => $dn,
358             scope => 'one',
359             filter => '(&(objectClass=' . $Kolab::config{$p . '_object_class'} . ")$add(" . $Kolab::config{$p . '_field_deleted'} . '=TRUE))',
360             attrs => [
361             '*',
362             $Kolab::config{$p . '_field_guid'},
363             $Kolab::config{$p . '_field_modified'},
364             $Kolab::config{$p . '_field_quota'},
365             $Kolab::config{$p . '_field_deleted'},
366             ],
367             );
368              
369             if ($ldapmesg->code <= 0) {
370             foreach $ldapobject ($ldapmesg->entries) {
371             deleteObject($ldap, $cyrus, $ldapobject, 1, $p);
372             }
373             } else {
374             Kolab::log('L', "Unable to locate deleted `$p' objects in DN `$dn'", KOLAB_WARN);
375             }
376              
377             # Now check that all objects in LDAP have corresponding mailboxes
378             # This also resurrects any missing users, if neccessary
379             $ldapmesg = $ldap->search(
380             base => $dn,
381             scope => 'one',
382             filter => '(&(objectClass=' . $Kolab::config{$p . '_object_class'} . ")$add)",
383             attrs => [
384             '*',
385             $Kolab::config{$p . '_field_guid'},
386             $Kolab::config{$p . '_field_modified'},
387             $Kolab::config{$p . '_field_quota'},
388             $Kolab::config{$p . '_field_deleted'},
389             ],
390             );
391              
392             if ($ldapmesg->code <= 0) {
393             foreach $ldapobject ($ldapmesg->entries) {
394             createObject($ldap, $cyrus, $ldapobject, 1, $p, $doacls);
395             }
396             } else {
397             Kolab::log('L', "Unable to locate `$p' objects in DN `$dn'", KOLAB_WARN);
398             }
399              
400             Kolab::log('L', "Finished synchronising `$p' DN `$dn'");
401             }
402              
403             &destroy($ldap);
404              
405             Kolab::log('L', "Finished `$p' object synchronisation");
406             }
407              
408             1;
409             __END__