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