]> err.no Git - scalable-opengroupware.org/blob - SOGo/SoObjects/SOGo/AgenorUserManager.m
dfafce34b51a6b58f9a596ca1f1c8622e3bc961e
[scalable-opengroupware.org] / SOGo / SoObjects / SOGo / AgenorUserManager.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "AgenorUserManager.h"
23 #include <NGExtensions/NGExtensions.h>
24 #include <NGLdap/NGLdap.h>
25 #include "SOGoLRUCache.h"
26
27 @interface AgenorUserManager (PrivateAPI)
28 - (NGLdapConnection *)ldapConnection;
29
30 - (void)_cacheCN:(NSString *)_cn forUID:(NSString *)_uid;
31 - (NSString *)_cachedCNForUID:(NSString *)_uid;
32 - (void)_cacheServer:(NSString *)_server forUID:(NSString *)_uid;
33 - (NSString *)_cachedServerForUID:(NSString *)_uid;
34 - (void)_cacheEmail:(NSString *)_email forUID:(NSString *)_uid;
35 - (NSString *)_cachedEmailForUID:(NSString *)_uid;
36 - (void)_cacheUID:(NSString *)_uid forEmail:(NSString *)_email;
37 - (NSString *)_cachedUIDForEmail:(NSString *)_email;
38   
39 @end
40
41 // TODO: add a timer to flush LRU caches every some hours
42
43 @implementation AgenorUserManager
44
45 static BOOL     debugOn     = NO;
46 static BOOL     useLDAP     = NO;
47 static NSString *ldapHost   = nil;
48 static NSString *ldapBaseDN = nil;
49 static NSString *fallbackIMAP4Server  = nil;
50 static NSString *defaultMailDomain    = @"equipement.gouv.fr";
51 static NSString *shareLDAPClass       = @"mineqMelBoite";
52 static NSString *shareLoginSeparator  = @".-.";
53 static NSString *mailEmissionAttrName = @"mineqMelmailEmission";
54
55 static NSArray *fromEMailAttrs = nil;
56
57 + (void)initialize {
58   static BOOL didInit = NO;
59   NSUserDefaults *ud;
60
61   if (didInit)
62     return;
63   didInit = YES;
64   ud      = [NSUserDefaults standardUserDefaults];
65   debugOn = [ud boolForKey:@"SOGoUserManagerDebugEnabled"];
66   
67   useLDAP = [ud boolForKey:@"SOGoUserManagerUsesLDAP"];
68   if (useLDAP) {
69     ldapHost   = [[ud stringForKey:@"SOGoLDAPHost"]   copy];
70     ldapBaseDN = [[ud stringForKey:@"SOGoLDAPBaseDN"] copy];
71     NSLog(@"Note: using LDAP host to manage accounts: %@", ldapHost);
72   }
73   else
74     NSLog(@"Note: LDAP access is disabled.");
75   
76   fallbackIMAP4Server = [[ud stringForKey:@"SOGoFallbackIMAP4Server"] copy];
77   if ([fallbackIMAP4Server length] > 0)
78     NSLog(@"Note: using fallback IMAP4 server: '%@'", fallbackIMAP4Server);
79   else
80     fallbackIMAP4Server = nil;
81
82   fromEMailAttrs = 
83     [[NSArray alloc] initWithObjects:mailEmissionAttrName, nil];
84 }
85
86 + (id)sharedUserManager {
87   static AgenorUserManager *mgr = nil;
88   if (mgr == nil)
89     mgr = [[self alloc] init];
90   return mgr;
91 }
92
93 - (id)init {
94   self = [super init];
95   if(self) {
96     self->serverCache     = [[SOGoLRUCache alloc] initWithCacheSize:10000];
97     self->cnCache         = [[SOGoLRUCache alloc] initWithCacheSize:10000];
98     self->uidCache        = [[SOGoLRUCache alloc] initWithCacheSize:10000];
99     self->emailCache      = [[SOGoLRUCache alloc] initWithCacheSize:10000];
100     self->shareStoreCache = [[SOGoLRUCache alloc] initWithCacheSize:10000];
101     self->shareEMailCache = [[SOGoLRUCache alloc] initWithCacheSize:10000];
102   }
103   return self;
104 }
105
106 - (void)dealloc {
107   [self->shareStoreCache release];
108   [self->shareEMailCache release];
109   [self->serverCache     release];
110   [self->cnCache         release];
111   [self->uidCache        release];
112   [self->emailCache      release];
113   [super dealloc];
114 }
115
116 - (NGLdapConnection *)ldapConnection {
117   static NGLdapConnection *ldapConnection = nil;
118   if(!ldapConnection) {
119     ldapConnection = [[NGLdapConnection alloc] initWithHostName:ldapHost];
120 #if 0
121     [ldapConnection setUseCache:YES];
122 #endif
123   }
124   return ldapConnection;
125 }
126
127
128 /* private cache helpers */
129 // TODO: this is really unnecessary, no?
130
131 - (void)_cacheCN:(NSString *)_cn forUID:(NSString *)_uid {
132   if (_cn == nil) return;
133   [self->cnCache addObject:_cn forKey:_uid];
134 }
135 - (NSString *)_cachedCNForUID:(NSString *)_uid {
136   return [self->cnCache objectForKey:_uid];
137 }
138
139 - (void)_cacheServer:(NSString *)_server forUID:(NSString *)_uid {
140   if (_server == nil) return;
141   [self->serverCache addObject:_server forKey:_uid];
142 }
143 - (NSString *)_cachedServerForUID:(NSString *)_uid {
144   return [self->serverCache objectForKey:_uid];
145 }
146
147 - (void)_cacheEmail:(NSString *)_email forUID:(NSString *)_uid {
148   if (_email == nil) return;
149   [self->emailCache addObject:_email forKey:_uid];
150 }
151 - (NSString *)_cachedEmailForUID:(NSString *)_uid {
152   return [self->emailCache objectForKey:_uid];
153 }
154
155 - (void)_cacheUID:(NSString *)_uid forEmail:(NSString *)_email {
156   if (_uid == nil) return;
157   [self->uidCache addObject:_uid forKey:_email];
158 }
159 - (NSString *)_cachedUIDForEmail:(NSString *)_email {
160   return [self->uidCache objectForKey:_email];
161 }
162
163
164 /* uid <-> email mapping */
165
166 /*
167  UPDATE: the email excerpt below has been marked by Maxime as being
168          wrong. This algorithm can not be expected to work, thus
169          the mapping has been replaced with an LDAP query.
170
171  --- snip ---
172  The uid field is in bijection this the email adress :
173  this field can be construct from the email. Email are uniques.
174  
175  So, we can use email adresse from identifier. 
176  The field is made like this :
177  _ if the email is equipement.gouv.fr then the login
178  is the part before the @
179  for example : fisrtName.lastName
180  _ if the email is not equipement.gouv.fr then the login
181  is the full email adress where @ is change to . (dot)
182  for example : fisrtName.lastName.subDomain.domain.tld
183  --- snap ---
184
185  NOTE: mapping email -> uid is easy, but can also generate uid's not known
186        to the system (i.e. for private addressbook entries, obvious).
187        The reverse mapping can work _only_ if "firstName.lastname." is
188        guaranteed, because the second dot would be mapped to '@'. This
189        is probably error prone.
190        Only LDAP fetches would guarantee correctness in both cases.
191 */
192
193 - (NSString *)primaryGetAgenorUIDForEmail:(NSString *)_email {
194   static NSArray   *uidAttrs = nil;
195   NGLdapConnection *conn;
196   EOQualifier      *q;
197   NSEnumerator     *resultEnum;
198   NGLdapEntry      *entry;
199   NGLdapAttribute  *uidAttr;
200   NSString         *uid;
201
202   if (uidAttrs == nil)
203     uidAttrs = [[NSArray alloc] initWithObjects:@"uid", nil];
204     
205   q = [EOQualifier qualifierWithQualifierFormat:@"mail = %@", _email];
206     
207   conn       = [self ldapConnection];
208   resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
209                      qualifier:q
210                      attributes:uidAttrs];
211   entry = [resultEnum nextObject];
212   if (entry == nil) {
213     if(debugOn) {
214       [self logWithFormat:@"%s Didn't find LDAP entry for email '%@'!",
215             __PRETTY_FUNCTION__,
216             _email];
217     }
218     return nil;
219   }
220   uidAttr = [entry attributeWithName:@"uid"];
221   if (!uidAttr)
222     return nil; /* can happen, not unlikely */
223   uid = [uidAttr stringValueAtIndex:0];
224   return uid;
225 }
226
227 - (NSString *)getUIDForEmail:(NSString *)_email {
228   NSString *uid;
229   
230   if ((uid = [self _cachedUIDForEmail:_email]) != nil)
231     return uid;
232     
233   if (useLDAP) {
234     uid = [self primaryGetAgenorUIDForEmail:_email];
235   }
236   else {
237     NSRange r;
238     NSString *domain;
239     
240     if(!_email || [_email length] == 0)
241       return nil;
242     
243     r = [_email rangeOfString:@"@"];
244     if (r.length == 0)
245       return nil;
246     
247     domain = [_email substringFromIndex:NSMaxRange(r)];
248     if (![domain isEqualToString:defaultMailDomain])
249       uid = _email;
250     else
251       uid = [_email substringToIndex:r.location];
252   }
253   
254   [self _cacheUID:uid forEmail:_email];
255   return uid;
256 }
257
258 - (NSString *)primaryGetEmailForAgenorUID:(NSString *)_uid {
259   NGLdapConnection *conn;
260   EOQualifier      *q;
261   NSEnumerator     *resultEnum;
262   NGLdapEntry      *entry;
263   NGLdapAttribute  *emailAttr;
264   NSString         *email;
265   unsigned         count;
266     
267   q = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _uid];
268     
269   conn       = [self ldapConnection];
270   resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
271                      qualifier:q
272                      attributes:fromEMailAttrs];
273   entry = [resultEnum nextObject];
274   if (entry == nil) {
275       if(debugOn) {
276         [self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
277                               __PRETTY_FUNCTION__,
278                               _uid];
279       }
280       return nil;
281   }
282   emailAttr = [entry attributeWithName:mailEmissionAttrName];
283   if (emailAttr == nil)
284     return nil; /* shit happens */
285   
286   email = nil;
287   count = [emailAttr count];
288 #if 0 // TODO: explain why this is commented out!
289   if (count > 1) {
290         unsigned i;
291
292         /* in case there are multiple email addresses, select the first
293            which doesn't have '@equipement.gouv.fr' in it */
294         for (i = 0; i < count; i++) {
295           NSString *candidate;
296           
297           candidate = [emailAttr stringValueAtIndex:i];
298           if (![candidate hasSuffix:defaultMailDomain]) {
299             // TODO: also check for '@'
300             email = candidate;
301             break;
302           }
303         }
304   }
305 #endif
306   if (email == nil && count > 0)
307     email = [emailAttr stringValueAtIndex:0];
308   
309   return email;
310 }
311
312 - (NSString *)getEmailForUID:(NSString *)_uid {
313   NSString *email;
314   
315   if (![_uid isNotNull] || [_uid length] == 0)
316     return nil;
317   if ((email = [self _cachedEmailForUID:_uid]) != nil)
318     return email;
319   
320   if (useLDAP) {
321     email = [self primaryGetEmailForAgenorUID:_uid];
322   }
323   else {
324     NSRange r;
325     
326     r = [_uid rangeOfString:@"@"];
327     email = (r.length > 0)
328       ? _uid
329       : [[_uid stringByAppendingString:@"@"]
330                stringByAppendingString:defaultMailDomain];
331   }
332   
333   [self _cacheEmail:email forUID:_uid];
334   return email;
335 }
336
337
338 /* CN */
339
340 - (NSString *)primaryGetCNForAgenorUID:(NSString *)_uid {
341   static NSArray   *cnAttrs = nil;
342   NGLdapConnection *conn;
343   EOQualifier      *q;
344   NSEnumerator     *resultEnum;
345   NGLdapEntry      *entry;
346   NGLdapAttribute  *cnAttr;
347   NSString         *cn;
348
349   if (cnAttrs == nil)
350     cnAttrs = [[NSArray alloc] initWithObjects:@"cn", nil];
351   
352   q = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _uid];
353   
354   conn = [self ldapConnection];
355   resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
356                      qualifier:q
357                      attributes:cnAttrs];
358   entry = [resultEnum nextObject];
359   if (entry == nil) {
360       if(debugOn) {
361         [self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
362                               __PRETTY_FUNCTION__,
363                               _uid];
364       }
365       return nil;
366   }
367   cnAttr = [entry attributeWithName:@"cn"];
368   if(cnAttr == nil && debugOn) {
369       [self logWithFormat:@"%s LDAP entry for uid '%@' has no common name?",
370                             __PRETTY_FUNCTION__,
371                             _uid];
372       return nil; /* nothing we can do about it */
373   }
374   cn = [cnAttr stringValueAtIndex:0];
375   return cn;
376 }
377
378 - (NSString *)getCNForUID:(NSString *)_uid {
379   NSString *cn;
380
381   if ((cn = [self _cachedCNForUID:_uid]) != nil)
382     return cn;
383   
384   if (useLDAP) {
385     cn = [self primaryGetCNForAgenorUID:_uid];
386   }
387   else {
388     NSString *s;
389     NSRange  r;
390     
391     s = _uid;
392     if ([s length] < 10)
393       return s;
394     
395     // TODO: algorithm might be inappropriate, depends on the actual UID
396     r = [s rangeOfString:@"."];
397     if (r.length == 0)
398       cn = s;
399     else
400       cn = [s substringToIndex:r.location];
401   }
402   
403   [self _cacheCN:cn forUID:_uid];
404   return cn;
405 }
406
407
408 /* Servers, IMAP */
409
410 - (NSString *)getIMAPAccountStringForUID:(NSString *)_uid {
411   NSString *server;
412   
413   server = [self getServerForUID:_uid];
414   if (server == nil)
415     return nil;
416   return [NSString stringWithFormat:@"%@@%@", _uid, server];
417 }
418
419 - (NSArray *)mailServerDiscoveryAttributes {
420   static NSArray *attrs = nil;
421
422   if (attrs == nil) {
423     attrs = [[NSArray alloc] initWithObjects:
424                                @"uid", /* required for shares */
425                                @"mineqMelRoutage", 
426                                @"mineqMelServeurPrincipal",
427                              nil];
428   }
429   return attrs;
430 }
431
432 - (NGLdapEntry *)_fetchEntryForAgenorUID:(NSString *)_uid {
433   // TODO: badly named, this fetches the mail server discovery attributes
434   /* called by -primaryGetServerForAgenorUID: */
435   NGLdapConnection *conn;
436   EOQualifier      *q;
437   NSEnumerator     *resultEnum;
438   NGLdapEntry      *entry;
439   
440   q = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _uid];
441   
442   conn = [self ldapConnection];
443   resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
444                      qualifier:q
445                      attributes:[self mailServerDiscoveryAttributes]];
446   /* we just expect one entry, thus drop the rest */
447   entry = [resultEnum nextObject];
448   if (entry == nil) {
449       if(debugOn) {
450         NSLog(@"%s Didn't find LDAP entry for uid '%@'!",
451               __PRETTY_FUNCTION__,
452               _uid);
453       }
454       return nil;
455   }
456   return entry;
457 }
458
459 - (NSArray *)_serverCandidatesForMineqMelRoutage:(NGLdapAttribute *)attr {
460   /*
461     eg:
462       "Baluh.Hommes.Tests-Montee-En-Charge-Ogo%equipement.gouv.fr@\
463        amelie-01.ac.melanie2.i2"
464   */
465   NSMutableArray *serverCandidates;
466   unsigned i, count;
467
468   count            = [attr count];
469   serverCandidates = [NSMutableArray arrayWithCapacity:count];
470   for (i = 0; i < count; i++) {
471     NSRange  r;
472     NSString *route;
473     unsigned length;
474     unsigned start;
475     NSRange  serverNameRange;
476     NSString *serverName;
477     
478     route = [attr stringValueAtIndex:i];
479
480     /* check for melanie suffix and ignore other entries */
481     
482     r = [route rangeOfString:@".melanie2.i2" options:NSBackwardsSearch];
483     if (r.length == 0) {
484 #if 0
485       [self logWithFormat:@"found no melanie in route: '%@'", route];
486 #endif
487       continue;
488     }
489
490     /* check for @ inside the string, searching backwards (ignoring suffix) */
491     
492     // be clever: TODO: in what way is this clever?
493     length = [route length];
494     r = NSMakeRange(0, length - r.length); /* cut of suffix (.melanie2.i2) */
495     r = [route rangeOfString:@"@" options:NSBackwardsSearch range:r];
496     if (r.length == 0) {
497 #if 0
498       [self logWithFormat:@"found no @ in route: '%@'", route];
499 #endif
500       continue;
501     }
502     
503     /* check for percent sign */
504     
505     start = NSMaxRange(r); /* start behind the @ */
506     
507     /* this range covers everything after @: 'amelie-01.ac.melanie2.i2' */
508     serverNameRange = NSMakeRange(start, length - start);
509     
510     /* and this range covers everything to the @ */
511     r = NSMakeRange(0, start - 1);
512     r = [route rangeOfString:@"%" options:NSBackwardsSearch range:r];
513     if (r.length == 0) {
514 #if 0
515       [self logWithFormat:@"found no %% in route: '%@' / '%@'", 
516             route, [route substringWithRange:NSMakeRange(0, length - start)]];
517 #endif
518       continue;
519     }
520     
521     serverName = [route substringWithRange:serverNameRange];
522     [serverCandidates addObject:serverName];
523   }
524   return serverCandidates;
525 }
526
527 - (NSString *)serverFromEntry:(NGLdapEntry *)_entry {
528   NSString        *server;
529   NGLdapAttribute *attr;
530
531   server = nil;
532   
533   attr = [_entry attributeWithName:@"mineqMelRoutage"];
534   if (attr != nil) {
535     NSArray *serverCandidates;
536     
537     serverCandidates = [self _serverCandidatesForMineqMelRoutage:attr];
538     if ([serverCandidates count] > 0)
539       server = [serverCandidates objectAtIndex:0];
540     
541     if ([serverCandidates count] > 1) {
542       [self logWithFormat:
543               @"WARNING: more than one value for 'mineqMelRoutage': %@",
544               serverCandidates];
545     }
546   }
547   else {
548     [self debugWithFormat:
549             @"%s LDAP entry '%@' has no mineqMelRoutage entry?",
550             __PRETTY_FUNCTION__, [_entry dn]];
551   }
552   
553   /* last resort */
554   if (server == nil) {
555     attr = [_entry attributeWithName:@"mineqMelServeurPrincipal"];
556     if ([attr count] > 0)
557       server = [attr stringValueAtIndex:0];
558   }
559   
560   return server;
561 }
562
563 - (NSString *)primaryGetServerForAgenorUID:(NSString *)_uid {
564   /*
565    First of all : for a particular user IMAP and SMTP are served on the same
566    host.
567    
568    The name of the machine is determined by applying a regex on every values of
569    the mineqMelRoutage LDAP attribute.
570    The regex is :   .*%.*@(.*\.melanie2\.i2$)
571    It extracts the substring that follows '@', ends with 'melanie2', on
572    adresses which have a '%' before the '@'
573    
574    Example: helge.hesse%opengroupware.org@servername1.melanie2.i2
575    -> servername1.melanie2.i2
576    
577    If only one server name is found by applying the regex on every value of the
578    attribute, then this name is the IMAP/SMTP server for that user.
579    Note that this is the case when we got a unique (well formed) value for the
580    attribute.
581    If the regex finds more than one servername when applied to the differents
582    values, then the IMAP/SMTP server name is to be found in the
583    mineqMelServeurPrincipal attribute of the user.
584   */
585   NSString    *server;
586   NGLdapEntry *entry;
587   
588   if ((entry = [self _fetchEntryForAgenorUID:_uid]) == nil)
589     return nil;
590   
591   if ((server = [self serverFromEntry:entry]) != nil)
592     return server;
593   
594   [self debugWithFormat:
595           @"%s no chance of getting at server info for user '%@', "
596           @"tried everything. Sorry.",
597           __PRETTY_FUNCTION__, _uid];
598   return nil;
599 }
600
601 - (NSString *)getServerForUID:(NSString *)_uid {
602   NSString *server;
603
604   if (_uid == nil || [_uid length] == 0)
605     return nil;
606   
607   if ((server = [self _cachedServerForUID:_uid]) != nil)
608     return server;
609   
610   if (useLDAP)
611     server = [self primaryGetServerForAgenorUID:_uid];
612   else if (fallbackIMAP4Server != nil)
613     server = fallbackIMAP4Server;
614   else {
615     [self logWithFormat:@"ERROR: could not get server for uid '%@', "
616           @"neither LDAP (SOGoUserManagerUsesLDAP) nor "
617           @"a fallback (SOGoFallbackIMAP4Server) is configured.",
618           _uid];
619     server = nil;
620   }
621   
622   [self _cacheServer:server forUID:_uid];
623   return server;
624 }
625
626 /* shared mailboxes */
627
628 - (NSArray *)getSharedMailboxAccountStringsForUID:(NSString *)_uid {
629   /*
630     Sample:
631       "(&(mineqMelPartages=guizmo.g:*)(objectclass=mineqMelBoite))"
632       "guizmo.g" is the uid of the user
633     
634     Login:
635       guizmo.g.-.baluh.hommes.tests-montee-en-charge-ogo
636       (uid + ".-." + share-uid)
637     
638     Note: shared mailboxes can be on different hosts!
639   */
640   NSMutableArray   *shares = nil;
641   NGLdapConnection *conn;
642   EOQualifier      *q;
643   NSString         *sharePattern;
644   NSEnumerator     *resultEnum;
645   NGLdapEntry      *entry;
646   
647   if ([_uid length] == 0)
648     return nil;
649   
650   if (!useLDAP) {
651     [self logWithFormat:
652             @"Note: LDAP access is disabled, returning no shared accounts."];
653     return nil;
654   }
655
656   /* check cache */
657   if ((shares = [self->shareStoreCache objectForKey:_uid]) != nil)
658     return shares;
659   
660   sharePattern = [_uid stringByAppendingString:@":*"];
661   
662   q = [EOQualifier qualifierWithQualifierFormat:
663                      @"(mineqMelPartages = %@) AND (objectclass = %@)",
664                      sharePattern, shareLDAPClass];
665   
666   conn = [self ldapConnection];
667
668   resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
669                      qualifier:q
670                      attributes:[self mailServerDiscoveryAttributes]];
671   
672   while ((entry = [resultEnum nextObject]) != nil) {
673     NSString *server, *shareLogin;
674     id shareUid;
675     
676     if ([(server = [self serverFromEntry:entry]) length] == 0) {
677       [self errorWithFormat:@"found no mail server host for share: %@",
678               [entry dn]];
679       continue;
680     }
681     
682     shareUid = [entry attributeWithName:@"uid"];
683     if ([shareUid count] < 1) {
684       [self errorWithFormat:@"found no 'uid' for share: %@", [entry dn]];
685       continue;
686     }
687     shareUid = [shareUid stringValueAtIndex:0];
688     
689     shareLogin = [_uid stringByAppendingString:shareLoginSeparator];
690     shareLogin = [shareLogin stringByAppendingString:shareUid];
691     
692     if (shares == nil)
693       shares = [NSMutableArray arrayWithCapacity:4];
694     
695     shareLogin = [shareLogin stringByAppendingString:@"@"];
696     shareLogin = [shareLogin stringByAppendingString:server];
697     [shares addObject:shareLogin];
698   }
699   
700   /* ensure that ordering is always the same */
701   [shares sortUsingSelector:@selector(compare:)];
702   
703   /* cache */
704   shares = (shares == nil) ? [NSArray array] : [[shares copy] autorelease];
705   [self->shareStoreCache addObject:shares forKey:_uid];
706   return shares;
707 }
708
709 - (NSArray *)getSharedMailboxEMailsForUID:(NSString *)_uid {
710   NSMutableArray   *shares = nil;
711   NGLdapConnection *conn;
712   EOQualifier      *q;
713   NSString         *gPattern, *cPattern;
714   NSEnumerator     *resultEnum;
715   NGLdapEntry      *entry;
716   
717   if ([_uid length] == 0)
718     return nil;
719   
720   if (!useLDAP) {
721     [self logWithFormat:
722             @"Note: LDAP access is disabled, returning no shared froms."];
723     return nil;
724   }
725   
726   /* check cache */
727   if ((shares = [self->shareEMailCache objectForKey:_uid]) != nil)
728     return shares;
729   
730   /* G and C mean "emission access" */
731   gPattern = [_uid stringByAppendingString:@":G"];
732   cPattern = [_uid stringByAppendingString:@":C"];
733   
734   q = [EOQualifier qualifierWithQualifierFormat:
735                      @"((mineqMelPartages = %@) OR (mineqMelPartages = %@)) "
736                      @"AND (objectclass = %@)",
737                    gPattern, cPattern, shareLDAPClass];
738   
739   conn = [self ldapConnection];
740   
741   resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
742                      qualifier:q
743                      attributes:fromEMailAttrs];
744   
745   while ((entry = [resultEnum nextObject]) != nil) {
746     id emissionAttr;
747     
748     emissionAttr = [entry attributeWithName:mailEmissionAttrName];
749     if ([emissionAttr count] == 0) {
750       [self logWithFormat:@"WARNING: share has no %@ attr: %@",
751               mailEmissionAttrName, [entry dn]];
752       continue;
753     }
754     
755     if ([emissionAttr count] > 1) {
756       [self logWithFormat:
757               @"WARNING: share has more than one value in %@ attr: %@",
758               mailEmissionAttrName, [entry dn]];
759       continue;
760     }
761     
762     emissionAttr = [emissionAttr stringValueAtIndex:0];
763     if (shares == nil) shares = [NSMutableArray arrayWithCapacity:4];
764     [shares addObject:emissionAttr];
765   }
766   
767   /* ensure that ordering is always the same */
768   [shares sortUsingSelector:@selector(compare:)];
769   
770   /* cache */
771   shares = (shares == nil) ? [NSArray array] : [[shares copy] autorelease];
772   [self->shareEMailCache addObject:shares forKey:_uid];
773   return shares;
774 }
775
776 /* free busy */
777
778 - (NSURL *)getFreeBusyURLForUID:(NSString *)_uid {
779   [self logWithFormat:@"TODO(%s): implement", __PRETTY_FUNCTION__];
780   return nil;
781 }
782
783 /* defaults */
784
785 - (NSUserDefaults *)getUserDefaultsForUID:(NSString *)_uid {
786   [self logWithFormat:@"TODO: implement!"];
787   return nil;
788 }
789
790 /* debugging */
791
792 - (BOOL)isDebuggingEnabled {
793   return debugOn;
794 }
795
796 /* description */
797
798 - (NSString *)description {
799   NSMutableString *ms;
800   
801   ms = [NSMutableString stringWithCapacity:16];
802   [ms appendFormat:@"<0x%08X[%@]>", self, NSStringFromClass([self class])];
803   return ms;
804 }
805
806 @end /* AgenorUserManager */