2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
22 #include "AgenorUserManager.h"
23 #include "AgenorUserDefaults.h"
24 #include <NGExtensions/NGExtensions.h>
25 #include <NGLdap/NGLdap.h>
26 #include <NGiCal/NGiCal.h>
27 #include "SOGoLRUCache.h"
29 @interface AgenorUserManager (PrivateAPI)
30 - (NGLdapConnection *)ldapConnection;
32 - (void)_cacheCN:(NSString *)_cn forUID:(NSString *)_uid;
33 - (NSString *)_cachedCNForUID:(NSString *)_uid;
34 - (void)_cacheServer:(NSString *)_server forUID:(NSString *)_uid;
35 - (NSString *)_cachedServerForUID:(NSString *)_uid;
36 - (void)_cacheEmail:(NSString *)_email forUID:(NSString *)_uid;
37 - (NSString *)_cachedEmailForUID:(NSString *)_uid;
38 - (void)_cacheUID:(NSString *)_uid forEmail:(NSString *)_email;
39 - (NSString *)_cachedUIDForEmail:(NSString *)_email;
41 - (BOOL)primaryIsUserAllowedToChangeSOGoInternetAccess:(NSString *)_uid;
44 // TODO: add a timer to flush LRU caches every some hours
46 @implementation AgenorUserManager
48 static BOOL debugOn = NO;
49 static BOOL useLDAP = NO;
50 static NSString *ldapHost = nil;
51 static NSString *ldapBaseDN = nil;
52 static NSNull *sharedNull = nil;
53 static NSString *fallbackIMAP4Server = nil;
54 static NSString *defaultMailDomain = @"equipement.gouv.fr";
55 static NSString *shareLDAPClass = @"mineqMelBoite";
56 static NSString *shareLoginSeparator = @".-.";
57 static NSString *mailEmissionAttrName = @"mineqMelmailEmission";
58 static NSString *changeInternetAccessAttrName = @"mineqOgoAccesInternet";
59 static NSURL *AgenorProfileURL = nil;
61 static NSArray *fromEMailAttrs = nil;
64 static BOOL didInit = NO;
71 ud = [NSUserDefaults standardUserDefaults];
72 debugOn = [ud boolForKey:@"SOGoUserManagerDebugEnabled"];
74 useLDAP = [ud boolForKey:@"SOGoUserManagerUsesLDAP"];
76 ldapHost = [[ud stringForKey:@"SOGoLDAPHost"] copy];
77 ldapBaseDN = [[ud stringForKey:@"SOGoLDAPBaseDN"] copy];
78 NSLog(@"Note: using LDAP host to manage accounts: %@", ldapHost);
81 NSLog(@"Note: LDAP access is disabled.");
83 fallbackIMAP4Server = [[ud stringForKey:@"SOGoFallbackIMAP4Server"] copy];
84 if ([fallbackIMAP4Server length] > 0)
85 NSLog(@"Note: using fallback IMAP4 server: '%@'", fallbackIMAP4Server);
87 fallbackIMAP4Server = nil;
90 [[NSArray alloc] initWithObjects:mailEmissionAttrName, nil];
92 sharedNull = [[NSNull null] retain];
94 /* profile database URL */
96 if ((tmp = [ud stringForKey:@"AgenorProfileURL"]) == nil)
97 NSLog(@"ERROR: no 'AgenorProfileURL' database URL configured!");
98 else if ((AgenorProfileURL = [[NSURL alloc] initWithString:tmp]) == nil)
99 NSLog(@"ERROR: could not parse AgenorProfileURL: '%@'", tmp);
101 NSLog(@"Note: using profile at: %@", [AgenorProfileURL absoluteString]);
104 + (id)sharedUserManager {
105 static AgenorUserManager *mgr = nil;
107 mgr = [[self alloc] init];
114 self->serverCache = [[SOGoLRUCache alloc] initWithCacheSize:10000];
115 self->cnCache = [[SOGoLRUCache alloc] initWithCacheSize:10000];
116 self->uidCache = [[SOGoLRUCache alloc] initWithCacheSize:10000];
117 self->emailCache = [[SOGoLRUCache alloc] initWithCacheSize:10000];
118 self->shareStoreCache = [[SOGoLRUCache alloc] initWithCacheSize:10000];
119 self->shareEMailCache = [[SOGoLRUCache alloc] initWithCacheSize:10000];
120 self->changeInternetAccessCache =
121 [[SOGoLRUCache alloc] initWithCacheSize:10000];
127 [self->serverCache release];
128 [self->cnCache release];
129 [self->uidCache release];
130 [self->emailCache release];
131 [self->shareStoreCache release];
132 [self->shareEMailCache release];
133 [self->changeInternetAccessCache release];
137 - (NGLdapConnection *)ldapConnection {
138 static NGLdapConnection *ldapConnection = nil;
139 if(!ldapConnection) {
140 ldapConnection = [[NGLdapConnection alloc] initWithHostName:ldapHost];
142 [ldapConnection setUseCache:YES];
145 return ldapConnection;
149 /* private cache helpers */
150 // TODO: this is really unnecessary, no?
152 - (void)_cacheCN:(NSString *)_cn forUID:(NSString *)_uid {
153 if (_cn == nil) return;
154 [self->cnCache addObject:_cn forKey:_uid];
156 - (NSString *)_cachedCNForUID:(NSString *)_uid {
157 return [self->cnCache objectForKey:_uid];
160 - (void)_cacheServer:(NSString *)_server forUID:(NSString *)_uid {
161 if (_server == nil) return;
162 [self->serverCache addObject:_server forKey:_uid];
164 - (NSString *)_cachedServerForUID:(NSString *)_uid {
165 return [self->serverCache objectForKey:_uid];
168 - (void)_cacheEmail:(NSString *)_email forUID:(NSString *)_uid {
169 if (_email == nil) return;
170 [self->emailCache addObject:_email forKey:_uid];
172 - (NSString *)_cachedEmailForUID:(NSString *)_uid {
173 return [self->emailCache objectForKey:_uid];
176 - (void)_cacheUID:(NSString *)_uid forEmail:(NSString *)_email {
177 if (_uid == nil) return;
178 [self->uidCache addObject:_uid forKey:_email];
180 - (NSString *)_cachedUIDForEmail:(NSString *)_email {
181 return [self->uidCache objectForKey:_email];
185 /* uid <-> email mapping */
188 UPDATE: the email excerpt below has been marked by Maxime as being
189 wrong. This algorithm can not be expected to work, thus
190 the mapping has been replaced with an LDAP query.
193 The uid field is in bijection this the email adress :
194 this field can be construct from the email. Email are uniques.
196 So, we can use email adresse from identifier.
197 The field is made like this :
198 _ if the email is equipement.gouv.fr then the login
199 is the part before the @
200 for example : fisrtName.lastName
201 _ if the email is not equipement.gouv.fr then the login
202 is the full email adress where @ is change to . (dot)
203 for example : fisrtName.lastName.subDomain.domain.tld
206 NOTE: mapping email -> uid is easy, but can also generate uid's not known
207 to the system (i.e. for private addressbook entries, obvious).
208 The reverse mapping can work _only_ if "firstName.lastname." is
209 guaranteed, because the second dot would be mapped to '@'. This
210 is probably error prone.
211 Only LDAP fetches would guarantee correctness in both cases.
214 - (NSString *)primaryGetAgenorUIDForEmail:(NSString *)_email {
215 static NSArray *uidAttrs = nil;
216 NGLdapConnection *conn;
218 NSEnumerator *resultEnum;
220 NGLdapAttribute *uidAttr;
224 uidAttrs = [[NSArray alloc] initWithObjects:@"uid", nil];
226 q = [EOQualifier qualifierWithQualifierFormat:@"mail = %@", _email];
228 conn = [self ldapConnection];
229 resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
231 attributes:uidAttrs];
232 entry = [resultEnum nextObject];
235 [self logWithFormat:@"%s Didn't find LDAP entry for email '%@'!",
241 uidAttr = [entry attributeWithName:@"uid"];
243 return nil; /* can happen, not unlikely */
244 uid = [uidAttr stringValueAtIndex:0];
248 - (NSString *)getUIDForEmail:(NSString *)_email {
251 if ((uid = [self _cachedUIDForEmail:_email]) != nil)
255 uid = [self primaryGetAgenorUIDForEmail:_email];
261 if(!_email || [_email length] == 0)
264 r = [_email rangeOfString:@"@"];
268 domain = [_email substringFromIndex:NSMaxRange(r)];
269 if (![domain isEqualToString:defaultMailDomain])
272 uid = [_email substringToIndex:r.location];
275 [self _cacheUID:uid forEmail:_email];
279 - (NSString *)getUIDForICalPerson:(iCalPerson *)_person {
280 return [self getUIDForEmail:[_person rfc822Email]];
283 /* may insert NSNulls into returned array */
284 - (NSArray *)getUIDsForICalPersons:(NSArray *)_persons
285 applyStrictMapping:(BOOL)_mapStrictly
290 count = [_persons count];
291 ma = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
292 for (i = 0; i < count; i++) {
296 p = [_persons objectAtIndex:i];
297 uid = [self getUIDForICalPerson:p];
301 else if (!uid && _mapStrictly) {
302 [ma addObject:sharedNull];
308 - (NSString *)primaryGetEmailForAgenorUID:(NSString *)_uid {
309 NGLdapConnection *conn;
311 NSEnumerator *resultEnum;
313 NGLdapAttribute *emailAttr;
317 q = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _uid];
319 conn = [self ldapConnection];
320 resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
322 attributes:fromEMailAttrs];
323 entry = [resultEnum nextObject];
326 [self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
332 emailAttr = [entry attributeWithName:mailEmissionAttrName];
333 if (emailAttr == nil)
334 return nil; /* shit happens */
337 count = [emailAttr count];
338 #if 0 // TODO: explain why this is commented out!
342 /* in case there are multiple email addresses, select the first
343 which doesn't have '@equipement.gouv.fr' in it */
344 for (i = 0; i < count; i++) {
347 candidate = [emailAttr stringValueAtIndex:i];
348 if (![candidate hasSuffix:defaultMailDomain]) {
349 // TODO: also check for '@'
356 if (email == nil && count > 0)
357 email = [emailAttr stringValueAtIndex:0];
362 - (NSString *)getEmailForUID:(NSString *)_uid {
365 if (![_uid isNotNull] || [_uid length] == 0)
367 if ((email = [self _cachedEmailForUID:_uid]) != nil)
371 email = [self primaryGetEmailForAgenorUID:_uid];
376 r = [_uid rangeOfString:@"@"];
377 email = (r.length > 0)
379 : [[_uid stringByAppendingString:@"@"]
380 stringByAppendingString:defaultMailDomain];
383 [self _cacheEmail:email forUID:_uid];
390 - (NSString *)primaryGetCNForAgenorUID:(NSString *)_uid {
391 static NSArray *cnAttrs = nil;
392 NGLdapConnection *conn;
394 NSEnumerator *resultEnum;
396 NGLdapAttribute *cnAttr;
400 cnAttrs = [[NSArray alloc] initWithObjects:@"cn", nil];
402 q = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _uid];
404 conn = [self ldapConnection];
405 resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
408 entry = [resultEnum nextObject];
411 [self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
417 cnAttr = [entry attributeWithName:@"cn"];
418 if(cnAttr == nil && debugOn) {
419 [self logWithFormat:@"%s LDAP entry for uid '%@' has no common name?",
422 return nil; /* nothing we can do about it */
424 cn = [cnAttr stringValueAtIndex:0];
428 - (NSString *)getCNForUID:(NSString *)_uid {
431 if ((cn = [self _cachedCNForUID:_uid]) != nil)
435 cn = [self primaryGetCNForAgenorUID:_uid];
445 // TODO: algorithm might be inappropriate, depends on the actual UID
446 r = [s rangeOfString:@"."];
450 cn = [s substringToIndex:r.location];
453 [self _cacheCN:cn forUID:_uid];
460 - (NSString *)getIMAPAccountStringForUID:(NSString *)_uid {
463 server = [self getServerForUID:_uid];
466 return [NSString stringWithFormat:@"%@@%@", _uid, server];
469 - (NSArray *)mailServerDiscoveryAttributes {
470 static NSArray *attrs = nil;
473 attrs = [[NSArray alloc] initWithObjects:
474 @"uid", /* required for shares */
476 @"mineqMelServeurPrincipal",
482 - (NGLdapEntry *)_fetchEntryForAgenorUID:(NSString *)_uid {
483 // TODO: badly named, this fetches the mail server discovery attributes
484 /* called by -primaryGetServerForAgenorUID: */
485 NGLdapConnection *conn;
487 NSEnumerator *resultEnum;
490 q = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _uid];
492 conn = [self ldapConnection];
493 resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
495 attributes:[self mailServerDiscoveryAttributes]];
496 /* we just expect one entry, thus drop the rest */
497 entry = [resultEnum nextObject];
500 NSLog(@"%s Didn't find LDAP entry for uid '%@'!",
509 - (NSArray *)_serverCandidatesForMineqMelRoutage:(NGLdapAttribute *)attr {
512 "Baluh.Hommes.Tests-Montee-En-Charge-Ogo%equipement.gouv.fr@\
513 amelie-01.ac.melanie2.i2"
515 NSMutableArray *serverCandidates;
518 count = [attr count];
519 serverCandidates = [NSMutableArray arrayWithCapacity:count];
520 for (i = 0; i < count; i++) {
525 NSRange serverNameRange;
526 NSString *serverName;
528 route = [attr stringValueAtIndex:i];
530 /* check for melanie suffix and ignore other entries */
532 r = [route rangeOfString:@".melanie2.i2" options:NSBackwardsSearch];
535 [self logWithFormat:@"found no melanie in route: '%@'", route];
540 /* check for @ inside the string, searching backwards (ignoring suffix) */
542 // be clever: TODO: in what way is this clever?
543 length = [route length];
544 r = NSMakeRange(0, length - r.length); /* cut of suffix (.melanie2.i2) */
545 r = [route rangeOfString:@"@" options:NSBackwardsSearch range:r];
548 [self logWithFormat:@"found no @ in route: '%@'", route];
553 /* check for percent sign */
555 start = NSMaxRange(r); /* start behind the @ */
557 /* this range covers everything after @: 'amelie-01.ac.melanie2.i2' */
558 serverNameRange = NSMakeRange(start, length - start);
560 /* and this range covers everything to the @ */
561 r = NSMakeRange(0, start - 1);
562 r = [route rangeOfString:@"%" options:NSBackwardsSearch range:r];
565 [self logWithFormat:@"found no %% in route: '%@' / '%@'",
566 route, [route substringWithRange:NSMakeRange(0, length - start)]];
571 serverName = [route substringWithRange:serverNameRange];
572 [serverCandidates addObject:serverName];
574 return serverCandidates;
577 - (NSString *)serverFromEntry:(NGLdapEntry *)_entry {
579 NGLdapAttribute *attr;
583 attr = [_entry attributeWithName:@"mineqMelRoutage"];
585 NSArray *serverCandidates;
587 serverCandidates = [self _serverCandidatesForMineqMelRoutage:attr];
588 if ([serverCandidates count] > 0)
589 server = [serverCandidates objectAtIndex:0];
591 if ([serverCandidates count] > 1) {
593 @"WARNING: more than one value for 'mineqMelRoutage': %@",
598 [self debugWithFormat:
599 @"%s LDAP entry '%@' has no mineqMelRoutage entry?",
600 __PRETTY_FUNCTION__, [_entry dn]];
605 attr = [_entry attributeWithName:@"mineqMelServeurPrincipal"];
606 if ([attr count] > 0)
607 server = [attr stringValueAtIndex:0];
613 - (NSString *)primaryGetServerForAgenorUID:(NSString *)_uid {
615 First of all : for a particular user IMAP and SMTP are served on the same
618 The name of the machine is determined by applying a regex on every values of
619 the mineqMelRoutage LDAP attribute.
620 The regex is : .*%.*@(.*\.melanie2\.i2$)
621 It extracts the substring that follows '@', ends with 'melanie2', on
622 adresses which have a '%' before the '@'
624 Example: helge.hesse%opengroupware.org@servername1.melanie2.i2
625 -> servername1.melanie2.i2
627 If only one server name is found by applying the regex on every value of the
628 attribute, then this name is the IMAP/SMTP server for that user.
629 Note that this is the case when we got a unique (well formed) value for the
631 If the regex finds more than one servername when applied to the differents
632 values, then the IMAP/SMTP server name is to be found in the
633 mineqMelServeurPrincipal attribute of the user.
638 if ((entry = [self _fetchEntryForAgenorUID:_uid]) == nil)
641 if ((server = [self serverFromEntry:entry]) != nil)
644 [self debugWithFormat:
645 @"%s no chance of getting at server info for user '%@', "
646 @"tried everything. Sorry.",
647 __PRETTY_FUNCTION__, _uid];
651 - (NSString *)getServerForUID:(NSString *)_uid {
654 if (_uid == nil || [_uid length] == 0)
657 if ((server = [self _cachedServerForUID:_uid]) != nil)
661 server = [self primaryGetServerForAgenorUID:_uid];
662 else if (fallbackIMAP4Server != nil)
663 server = fallbackIMAP4Server;
665 [self logWithFormat:@"ERROR: could not get server for uid '%@', "
666 @"neither LDAP (SOGoUserManagerUsesLDAP) nor "
667 @"a fallback (SOGoFallbackIMAP4Server) is configured.",
672 [self _cacheServer:server forUID:_uid];
676 /* shared mailboxes */
678 - (NSArray *)getSharedMailboxAccountStringsForUID:(NSString *)_uid {
681 "(&(mineqMelPartages=guizmo.g:*)(objectclass=mineqMelBoite))"
682 "guizmo.g" is the uid of the user
685 guizmo.g.-.baluh.hommes.tests-montee-en-charge-ogo
686 (uid + ".-." + share-uid)
688 Note: shared mailboxes can be on different hosts!
690 NSMutableArray *shares = nil;
691 NGLdapConnection *conn;
693 NSString *sharePattern;
694 NSEnumerator *resultEnum;
697 if ([_uid length] == 0)
702 @"Note: LDAP access is disabled, returning no shared accounts."];
707 if ((shares = [self->shareStoreCache objectForKey:_uid]) != nil)
710 sharePattern = [_uid stringByAppendingString:@":*"];
712 q = [EOQualifier qualifierWithQualifierFormat:
713 @"(mineqMelPartages = %@) AND (objectclass = %@)",
714 sharePattern, shareLDAPClass];
716 conn = [self ldapConnection];
718 resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
720 attributes:[self mailServerDiscoveryAttributes]];
722 while ((entry = [resultEnum nextObject]) != nil) {
723 NSString *server, *shareLogin;
726 if ([(server = [self serverFromEntry:entry]) length] == 0) {
727 [self errorWithFormat:@"found no mail server host for share: %@",
732 shareUid = [entry attributeWithName:@"uid"];
733 if ([shareUid count] < 1) {
734 [self errorWithFormat:@"found no 'uid' for share: %@", [entry dn]];
737 shareUid = [shareUid stringValueAtIndex:0];
739 shareLogin = [_uid stringByAppendingString:shareLoginSeparator];
740 shareLogin = [shareLogin stringByAppendingString:shareUid];
743 shares = [NSMutableArray arrayWithCapacity:4];
745 shareLogin = [shareLogin stringByAppendingString:@"@"];
746 shareLogin = [shareLogin stringByAppendingString:server];
747 [shares addObject:shareLogin];
750 /* ensure that ordering is always the same */
751 [shares sortUsingSelector:@selector(compare:)];
754 shares = (shares == nil) ? [NSArray array] : [[shares copy] autorelease];
755 [self->shareStoreCache addObject:shares forKey:_uid];
759 - (NSArray *)getSharedMailboxEMailsForUID:(NSString *)_uid {
760 NSMutableArray *shares = nil;
761 NGLdapConnection *conn;
763 NSString *gPattern, *cPattern;
764 NSEnumerator *resultEnum;
767 if ([_uid length] == 0)
772 @"Note: LDAP access is disabled, returning no shared froms."];
777 if ((shares = [self->shareEMailCache objectForKey:_uid]) != nil)
780 /* G and C mean "emission access" */
781 gPattern = [_uid stringByAppendingString:@":G"];
782 cPattern = [_uid stringByAppendingString:@":C"];
784 q = [EOQualifier qualifierWithQualifierFormat:
785 @"((mineqMelPartages = %@) OR (mineqMelPartages = %@)) "
786 @"AND (objectclass = %@)",
787 gPattern, cPattern, shareLDAPClass];
789 conn = [self ldapConnection];
791 resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
793 attributes:fromEMailAttrs];
795 while ((entry = [resultEnum nextObject]) != nil) {
798 emissionAttr = [entry attributeWithName:mailEmissionAttrName];
799 if ([emissionAttr count] == 0) {
800 [self logWithFormat:@"WARNING: share has no %@ attr: %@",
801 mailEmissionAttrName, [entry dn]];
805 if ([emissionAttr count] > 1) {
807 @"WARNING: share has more than one value in %@ attr: %@",
808 mailEmissionAttrName, [entry dn]];
812 emissionAttr = [emissionAttr stringValueAtIndex:0];
813 if (shares == nil) shares = [NSMutableArray arrayWithCapacity:4];
814 [shares addObject:emissionAttr];
817 /* ensure that ordering is always the same */
818 [shares sortUsingSelector:@selector(compare:)];
821 shares = (shares == nil) ? [NSArray array] : [[shares copy] autorelease];
822 [self->shareEMailCache addObject:shares forKey:_uid];
828 - (NSURL *)getFreeBusyURLForUID:(NSString *)_uid {
829 [self logWithFormat:@"TODO(%s): implement", __PRETTY_FUNCTION__];
835 - (NSUserDefaults *)getUserDefaultsForUID:(NSString *)_uid {
838 if (AgenorProfileURL == nil) {
839 [self warnWithFormat:
840 @"no profile configured, cannot retrieve defaults for user: '%@'",
845 /* Note: do not cache, otherwise updates can be quite tricky */
846 defaults = [[[AgenorUserDefaults alloc]
847 initWithTableURL:AgenorProfileURL uid:_uid] autorelease];
851 /* internet access lock */
853 - (BOOL)isUserAllowedToChangeSOGoInternetAccess:(NSString *)_uid {
856 bv = [self->changeInternetAccessCache objectForKey:_uid];
860 value = [self primaryIsUserAllowedToChangeSOGoInternetAccess:_uid];
861 bv = [NSNumber numberWithBool:value];
862 [self->changeInternetAccessCache addObject:bv forKey:_uid];
864 return [bv boolValue];
867 - (BOOL)primaryIsUserAllowedToChangeSOGoInternetAccess:(NSString *)_uid {
868 static NSArray *attrs = nil;
869 NGLdapConnection *conn;
871 NSEnumerator *resultEnum;
873 NGLdapAttribute *attr;
877 attrs = [[NSArray alloc] initWithObjects:changeInternetAccessAttrName, nil];
879 q = [EOQualifier qualifierWithQualifierFormat:@"uid = %@", _uid];
881 conn = [self ldapConnection];
882 resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
885 entry = [resultEnum nextObject];
888 [self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
894 attr = [entry attributeWithName:changeInternetAccessAttrName];
895 if(attr == nil && debugOn) {
896 [self logWithFormat:@"%s LDAP entry for uid '%@' "
897 @"has no mineqOgoAccesInternet attribute?",
900 return NO; /* nothing we can do about it */
902 value = [attr stringValueAtIndex:0];
903 return [value boolValue];
908 - (BOOL)isDebuggingEnabled {
914 - (NSString *)description {
917 ms = [NSMutableString stringWithCapacity:16];
918 [ms appendFormat:@"<0x%08X[%@]", self, NSStringFromClass([self class])];
919 [ms appendString:@">"];
923 @end /* AgenorUserManager */