1/* LDAPSource.m - this file is part of SOGo
2 *
3 * Copyright (C) 2007-2021 Inverse inc.
4 *
5 * This file is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * This file is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; see the file COPYING.  If not, write to
17 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
19 */
20
21
22
23#import <NGExtensions/NSObject+Logs.h>
24#import <EOControl/EOControl.h>
25#import <NGLdap/NGLdapConnection.h>
26#import <NGLdap/NGLdapAttribute.h>
27#import <NGLdap/NGLdapEntry.h>
28#import <NGLdap/NGLdapModification.h>
29#import <NGLdap/NSString+DN.h>
30
31#import "LDAPSourceSchema.h"
32#import "NSArray+Utilities.h"
33#import "NSString+Utilities.h"
34#import "NSString+Crypto.h"
35#import "SOGoCache.h"
36#import "SOGoSystemDefaults.h"
37#import "SOGoUser.h"
38#import "SOGoUserManager.h"
39
40#import "LDAPSource.h"
41
42static Class NSStringK;
43
44#define SafeLDAPCriteria(x) [[[x stringByReplacingString: @"\\" withString: @"\\\\"] \
45                                 stringByReplacingString: @"'" withString: @"\\'"] \
46                                 stringByReplacingString: @"%" withString: @"%%"]
47
48@implementation LDAPSource
49
50+ (void) initialize
51{
52  NSStringK = [NSString class];
53}
54
55//
56//
57//
58+ (id) sourceFromUDSource: (NSDictionary *) theSource
59                 inDomain: (NSString *) theDomain
60{
61  id source;
62
63  source = [[self alloc] initFromUDSource: theSource
64                                 inDomain: theDomain];
65  [source autorelease];
66
67  return source;
68}
69
70//
71//
72//
73- (id) init
74{
75  if ((self = [super init]))
76    {
77      _sourceID = nil;
78      _displayName = nil;
79
80      _bindDN = nil;
81      _password = nil;
82      _sourceBindDN = nil;
83      _sourceBindPassword = nil;
84      _hostname = nil;
85      _port = 389;
86      _encryption = nil;
87      _domain = nil;
88
89      _baseDN = nil;
90      _pristineBaseDN = nil;
91      _schema = nil;
92      _IDField = @"cn"; /* the first part of a user DN */
93      _CNField = @"cn";
94      _UIDField = @"uid";
95      _mailFields = [[NSArray arrayWithObject: @"mail"] retain];
96      _contactMapping = nil;
97      // "mail" expands to all entries of MailFieldNames
98      // "name" expands to sn, displayname and cn
99      _searchFields = [[NSArray arrayWithObjects: @"name", @"mail", @"telephonenumber", nil] retain];
100      _groupObjectClasses = [[NSArray arrayWithObjects: @"group", @"groupofnames", @"groupofuniquenames", @"posixgroup", nil] retain];
101      _IMAPHostField = nil;
102      _IMAPLoginField = nil;
103      _SieveHostField = nil;
104      _bindFields = nil;
105      _scope = @"sub";
106      _filter = nil;
107      _userPasswordAlgorithm = nil;
108      _listRequiresDot = YES;
109
110      _passwordPolicy = NO;
111      _updateSambaNTLMPasswords = NO;
112      _lookupFields = [NSArray arrayWithObject: @"*"];
113      [_lookupFields retain];
114
115      _kindField = nil;
116      _multipleBookingsField = nil;
117
118      _MSExchangeHostname = nil;
119
120      _modifiers = nil;
121    }
122
123  return self;
124}
125
126//
127//
128//
129- (void) dealloc
130{
131  [_schema release];
132  [_bindDN release];
133  [_password release];
134  [_sourceBindDN release];
135  [_sourceBindPassword release];
136  [_hostname release];
137  [_encryption release];
138  [_baseDN release];
139  [_pristineBaseDN release];
140  [_IDField release];
141  [_CNField release];
142  [_UIDField release];
143  [_contactMapping release];
144  [_mailFields release];
145  [_searchFields release];
146  [_groupObjectClasses release];
147  [_IMAPHostField release];
148  [_IMAPLoginField release];
149  [_SieveHostField release];
150  [_bindFields release];
151  [_filter release];
152  [_userPasswordAlgorithm release];
153  [_sourceID release];
154  [_modulesConstraints release];
155  [_scope release];
156  [_domain release];
157  [_kindField release];
158  [_multipleBookingsField release];
159  [_MSExchangeHostname release];
160  [_modifiers release];
161  [_displayName release];
162  [_lookupFields release];
163  [super dealloc];
164}
165
166//
167//
168//
169- (id) initFromUDSource: (NSDictionary *) udSource
170               inDomain: (NSString *) sourceDomain
171{
172  SOGoDomainDefaults *dd;
173  NSNumber *udQueryLimit, *udQueryTimeout, *udGroupExpansionEnabled, *dotValue;
174
175  if ((self = [self init]))
176    {
177      [self setSourceID: [udSource objectForKey: @"id"]];
178      [self setDisplayName: [udSource objectForKey: @"displayName"]];
179
180      [self setBindDN: [udSource objectForKey: @"bindDN"]
181             password: [udSource objectForKey: @"bindPassword"]
182             hostname: [udSource objectForKey: @"hostname"]
183                 port: [udSource objectForKey: @"port"]
184           encryption: [udSource objectForKey: @"encryption"]
185    bindAsCurrentUser: [udSource objectForKey: @"bindAsCurrentUser"]];
186
187      [self setBaseDN: [udSource objectForKey: @"baseDN"]
188              IDField: [udSource objectForKey: @"IDFieldName"]
189              CNField: [udSource objectForKey: @"CNFieldName"]
190             UIDField: [udSource objectForKey: @"UIDFieldName"]
191           mailFields: [udSource objectForKey: @"MailFieldNames"]
192         searchFields: [udSource objectForKey: @"SearchFieldNames"]
193   groupObjectClasses: [udSource objectForKey: @"GroupObjectClasses"]
194        IMAPHostField: [udSource objectForKey: @"IMAPHostFieldName"]
195       IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"]
196       SieveHostField: [udSource objectForKey: @"SieveHostFieldName"]
197           bindFields: [udSource objectForKey: @"bindFields"]
198         lookupFields: [udSource objectForKey: @"lookupFields"]
199            kindField: [udSource objectForKey: @"KindFieldName"]
200            andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]];
201
202      dotValue = [udSource objectForKey: @"listRequiresDot"];
203      if (dotValue)
204        [self setListRequiresDot: [dotValue boolValue]];
205      [self setContactMapping: [udSource objectForKey: @"mapping"]
206             andObjectClasses: [udSource objectForKey: @"objectClasses"]];
207
208      [self setModifiers: [udSource objectForKey: @"modifiers"]];
209      ASSIGN(_abOU, [udSource objectForKey: @"abOU"]);
210
211      if ([sourceDomain length])
212        {
213          NSMutableString *s;
214
215          dd = [SOGoDomainDefaults defaultsForDomain: sourceDomain];
216          ASSIGN(_domain, sourceDomain);
217
218          // We now look if we have a dynamic baseBN and if we have any value to swap
219          if ([_baseDN rangeOfString: @"%d"].location != NSNotFound)
220            {
221              s = [NSMutableString stringWithString: _baseDN];
222              [s replaceOccurrencesOfString: @"%d"  withString: _domain  options: 0  range: NSMakeRange(0, [s length])];
223              ASSIGN(_baseDN, s);
224            }
225        }
226      else
227        dd = [SOGoSystemDefaults sharedSystemDefaults];
228
229      _contactInfoAttribute
230        = [udSource objectForKey: @"SOGoLDAPContactInfoAttribute"];
231      if (!_contactInfoAttribute)
232        _contactInfoAttribute = [dd ldapContactInfoAttribute];
233      [_contactInfoAttribute retain];
234
235      udQueryLimit = [udSource objectForKey: @"SOGoLDAPQueryLimit"];
236      if (udQueryLimit)
237        _queryLimit = [udQueryLimit intValue];
238      else
239        _queryLimit = [dd ldapQueryLimit];
240
241      udQueryTimeout = [udSource objectForKey: @"SOGoLDAPQueryTimeout"];
242      if (udQueryTimeout)
243        _queryTimeout = [udQueryTimeout intValue];
244      else
245        _queryTimeout = [dd ldapQueryTimeout];
246
247      if ([[udSource allKeys] containsObject: @"SOGoLDAPGroupExpansionEnabled"])
248        {
249          udGroupExpansionEnabled = [udSource objectForKey: @"SOGoLDAPGroupExpansionEnabled"];
250          _groupExpansionEnabled = [udGroupExpansionEnabled boolValue];
251        }
252      else
253        _groupExpansionEnabled = [dd ldapGroupExpansionEnabled];
254
255      ASSIGN(_modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
256      ASSIGN(_filter, [udSource objectForKey: @"filter"]);
257      ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]);
258      ASSIGN(_scope, ([udSource objectForKey: @"scope"]
259                       ? [udSource objectForKey: @"scope"]
260                       : (id)@"sub"));
261
262      if (!_userPasswordAlgorithm)
263        _userPasswordAlgorithm = @"none";
264
265      if ([udSource objectForKey: @"passwordPolicy"])
266        _passwordPolicy = [[udSource objectForKey: @"passwordPolicy"] boolValue];
267
268      if ([udSource objectForKey: @"updateSambaNTLMPasswords"])
269        _updateSambaNTLMPasswords = [[udSource objectForKey: @"updateSambaNTLMPasswords"] boolValue];
270
271      ASSIGN(_MSExchangeHostname, [udSource objectForKey: @"MSExchangeHostname"]);
272    }
273
274  return self;
275}
276
277- (void) setBindDN: (NSString *) theDN
278{
279  //NSLog(@"Setting bind DN to %@", theDN);
280  ASSIGN(_bindDN, theDN);
281}
282
283- (NSString *) bindDN
284{
285  return _bindDN;
286}
287
288- (void) setBindPassword: (NSString *) thePassword
289{
290  ASSIGN(_password, thePassword);
291}
292
293- (NSString *) bindPassword
294{
295  return _password;
296}
297
298- (BOOL) bindAsCurrentUser
299{
300  return _bindAsCurrentUser;
301}
302
303- (void) setBindDN: (NSString *) newBindDN
304          password: (NSString *) newBindPassword
305          hostname: (NSString *) newBindHostname
306              port: (NSString *) newBindPort
307        encryption: (NSString *) newEncryption
308 bindAsCurrentUser: (NSString *) bindAsCurrentUser
309{
310  ASSIGN(_bindDN, newBindDN);
311  ASSIGN(_password, newBindPassword);
312  ASSIGN(_sourceBindDN, newBindDN);
313  ASSIGN(_sourceBindPassword, newBindPassword);
314
315  ASSIGN(_encryption, [newEncryption uppercaseString]);
316  if ([_encryption isEqualToString: @"SSL"])
317    _port = 636;
318  ASSIGN(_hostname, newBindHostname);
319  if (newBindPort)
320    _port = [newBindPort intValue];
321  _bindAsCurrentUser = [bindAsCurrentUser boolValue];
322}
323
324//
325//
326//
327- (void) setBaseDN: (NSString *) newBaseDN
328           IDField: (NSString *) newIDField
329           CNField: (NSString *) newCNField
330          UIDField: (NSString *) newUIDField
331        mailFields: (NSArray *) newMailFields
332      searchFields: (NSArray *) newSearchFields
333groupObjectClasses: (NSArray *) newGroupObjectClasses
334     IMAPHostField: (NSString *) newIMAPHostField
335    IMAPLoginField: (NSString *) newIMAPLoginField
336    SieveHostField: (NSString *) newSieveHostField
337        bindFields: (id) newBindFields
338      lookupFields: (NSArray *) newLookupFields
339         kindField: (NSString *) newKindField
340  andMultipleBookingsField: (NSString *) newMultipleBookingsField
341{
342  ASSIGN(_baseDN, [newBaseDN lowercaseString]);
343  ASSIGN(_pristineBaseDN, [newBaseDN lowercaseString]);
344  if (newIDField)
345    ASSIGN(_IDField, [newIDField lowercaseString]);
346  if (newCNField)
347    ASSIGN(_CNField, [newCNField lowercaseString]);
348  if (newUIDField)
349    ASSIGN(_UIDField, [newUIDField lowercaseString]);
350  if (newIMAPHostField)
351    ASSIGN(_IMAPHostField, [newIMAPHostField lowercaseString]);
352  if (newIMAPLoginField)
353    ASSIGN(_IMAPLoginField, [newIMAPLoginField lowercaseString]);
354  if (newSieveHostField)
355    ASSIGN(_SieveHostField, [newSieveHostField lowercaseString]);
356  if (newMailFields)
357    ASSIGN(_mailFields, newMailFields);
358  if (newSearchFields)
359    ASSIGN(_searchFields, newSearchFields);
360  if (newGroupObjectClasses)
361    ASSIGN(_groupObjectClasses, newGroupObjectClasses);
362  if (newBindFields)
363    {
364      // Before SOGo v1.2.0, bindFields was a comma-separated list
365      // of values. So it could be configured as:
366      //
367      // bindFields = foo;
368      // bindFields = "foo, bar, baz";
369      //
370      // SOGo v1.2.0 and upwards redefined that parameter as an array
371      // so we would have instead:
372      //
373      // bindFields = (foo);
374      // bindFields = (foo, bar, baz);
375      //
376      // We check for the old format and we support it.
377      if ([newBindFields isKindOfClass: [NSArray class]])
378        ASSIGN(_bindFields, newBindFields);
379      else
380        {
381          [self logWithFormat: @"WARNING: using old bindFields format - please update it"];
382          ASSIGN(_bindFields, [newBindFields componentsSeparatedByString: @","]);
383        }
384    }
385  if (newLookupFields)
386    ASSIGN(_lookupFields, newLookupFields);
387  if (newKindField)
388    ASSIGN(_kindField, [newKindField lowercaseString]);
389  if (newMultipleBookingsField)
390    ASSIGN(_multipleBookingsField, [newMultipleBookingsField lowercaseString]);
391}
392
393- (void) setListRequiresDot: (BOOL) theBool
394{
395  _listRequiresDot = theBool;
396}
397
398- (BOOL) listRequiresDot
399{
400  return _listRequiresDot;
401}
402
403- (NSArray *) searchFields
404{
405  return _searchFields;
406}
407
408- (void) setContactMapping: (NSDictionary *) theMapping
409          andObjectClasses: (NSArray *) theObjectClasses
410{
411  ASSIGN(_contactMapping, theMapping);
412  ASSIGN(_contactObjectClasses, theObjectClasses);
413}
414
415//
416//
417//
418- (BOOL) _setupEncryption: (NGLdapConnection *) theConnection
419{
420  BOOL rc;
421
422  if ([_encryption isEqualToString: @"SSL"])
423    rc = [theConnection useSSL];
424  else if ([_encryption isEqualToString: @"STARTTLS"])
425    rc = [theConnection startTLS];
426  else
427    {
428      [self errorWithFormat:
429        @"encryption scheme '%@' not supported:"
430        @" use 'SSL' or 'STARTTLS'", _encryption];
431      rc = NO;
432    }
433
434  return rc;
435}
436
437//
438//
439//
440- (id) connection
441{
442  NGLdapConnection *ldapConnection;
443  NSString *value, *key;
444  SOGoCache *cache;
445
446  NS_DURING
447    {
448      //NSLog(@"Creating NGLdapConnection instance for bindDN '%@'", _bindDN);
449      ldapConnection = [[NGLdapConnection alloc] initWithHostName: _hostname
450                                                             port: _port];
451      [ldapConnection autorelease];
452      if (![_encryption length] || [self _setupEncryption: ldapConnection])
453        {
454          [ldapConnection bindWithMethod: @"simple"
455                                  binddn: _bindDN
456                             credentials: _password];
457          if (_queryLimit > 0)
458            [ldapConnection setQuerySizeLimit: _queryLimit];
459          if (_queryTimeout > 0)
460            [ldapConnection setQueryTimeLimit: _queryTimeout];
461          if (!_schema)
462            {
463              _schema = [LDAPSourceSchema new];
464              cache = [SOGoCache sharedCache];
465              key = [NSString stringWithFormat: @"schema:%@", _sourceID];
466              value = [cache valueForKey: key];
467
468              if (value)
469                {
470                  [_schema setSchema: (NSMutableDictionary *)[value objectFromJSONString]];
471                }
472              else
473                {
474                  // We go check in the LDAP directory
475                  [_schema readSchemaFromConnection: ldapConnection];
476                  [cache setValue: [_schema jsonRepresentation] forKey: key];
477                }
478            }
479        }
480      else
481        ldapConnection = nil;
482    }
483  NS_HANDLER
484    {
485      [self errorWithFormat: @"Could not bind to the LDAP server %@ (%d) "
486                             @"using the bind DN: %@", _hostname, _port, _bindDN];
487      [self errorWithFormat: @"%@", localException];
488      ldapConnection = nil;
489    }
490  NS_ENDHANDLER;
491
492  return ldapConnection;
493}
494
495- (NGLdapConnection *) _ldapConnection
496{
497  return (NGLdapConnection *)[self connection];
498}
499
500- (NSString *) domain
501{
502  return _domain;
503}
504
505/* user management */
506- (EOQualifier *) _qualifierForBindFilter: (NSString *) theUID
507{
508  NSMutableString *qs;
509  NSString *escapedUid;
510  NSEnumerator *fields;
511  NSString *currentField;
512
513  qs = [NSMutableString string];
514
515  escapedUid = SafeLDAPCriteria(theUID);
516
517  fields = [_bindFields objectEnumerator];
518  while ((currentField = [fields nextObject]))
519    [qs appendFormat: @" OR (%@='%@')", currentField, escapedUid];
520
521  if (_filter && [_filter length])
522    [qs appendFormat: @" AND %@", _filter];
523
524  [qs deleteCharactersInRange: NSMakeRange(0, 4)];
525
526  return [EOQualifier qualifierWithQualifierFormat: qs];
527}
528
529- (NSString *) _fetchUserDNForLogin: (NSString *) theLogin
530{
531  NGLdapConnection *ldapConnection;
532  EOQualifier *qualifier;
533  NSEnumerator *entries;
534  NSArray *attributes;
535  NSString *userDN;
536
537  ldapConnection = [self _ldapConnection];
538  qualifier = [self _qualifierForBindFilter: theLogin];
539  attributes = [NSArray arrayWithObject: @"dn"];
540
541  if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
542    entries = [ldapConnection baseSearchAtBaseDN: _baseDN
543                                       qualifier: qualifier
544                                      attributes: attributes];
545  else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
546    entries = [ldapConnection flatSearchAtBaseDN: _baseDN
547                                       qualifier: qualifier
548                                      attributes: attributes];
549  else
550    entries = [ldapConnection deepSearchAtBaseDN: _baseDN
551                                       qualifier: qualifier
552                                      attributes: attributes];
553
554  userDN = [[entries nextObject] dn];
555
556  return userDN;
557}
558
559//
560//
561//
562- (BOOL) checkLogin: (NSString *) _login
563           password: (NSString *) _pwd
564               perr: (SOGoPasswordPolicyError *) _perr
565             expire: (int *) _expire
566              grace: (int *) _grace
567{
568  NGLdapConnection *bindConnection;
569  NSString *userDN;
570  BOOL didBind;
571
572  didBind = NO;
573
574  NS_DURING
575    if ([_login length] > 0 && [_pwd length] > 0)
576      {
577        // We check if SOGo admins have deviced a top-level SOGoUserSources with a dynamic base DN.
578        // This is a supported multi-domain configuration. We alter the baseDN in this case by extracting
579        // the domain from the login.
580        [self updateBaseDNFromLogin: _login];
581
582        bindConnection = [[NGLdapConnection alloc] initWithHostName: _hostname
583                                                               port: _port];
584        if (![_encryption length] || [self _setupEncryption: bindConnection])
585          {
586            if (_queryTimeout > 0)
587              [bindConnection setQueryTimeLimit: _queryTimeout];
588
589            userDN = [[SOGoCache sharedCache] distinguishedNameForLogin: _login];
590
591            if (!userDN)
592              {
593                if (_bindFields)
594                  {
595                    // We MUST always use the source's bindDN/password in
596                    // order to lookup the user's DN. This is important since
597                    // if we use bindAsCurrentUser, we could stay bound and
598                    // lookup the user's DN (for an other user that is trying
599                    // to log in) but not be able to do so due to ACLs in LDAP.
600                    [self setBindDN: _sourceBindDN];
601                    [self setBindPassword: _sourceBindPassword];
602                    userDN = [self _fetchUserDNForLogin: _login];
603                  }
604                else
605                  userDN = [NSString stringWithFormat: @"%@=%@,%@",
606                                     _IDField, [_login escapedForLDAPDN], _baseDN];
607              }
608
609            if (userDN)
610              {
611                if (!_passwordPolicy)
612                  didBind = [bindConnection bindWithMethod: @"simple"
613                                                    binddn: userDN
614                                               credentials: _pwd];
615                else
616                  didBind = [bindConnection bindWithMethod: @"simple"
617                                                    binddn: userDN
618                                               credentials: _pwd
619                                                      perr: (void *)_perr
620                                                    expire: _expire
621                                                     grace: _grace];
622
623                if (didBind)
624                  // We cache the _login <-> userDN entry to speed up things
625                  [[SOGoCache sharedCache] setDistinguishedName: userDN
626                                                       forLogin: _login];
627              }
628          }
629      }
630  NS_HANDLER
631    {
632      [self logWithFormat: @"%@", localException];
633    }
634  NS_ENDHANDLER;
635
636  [bindConnection release];
637  return didBind;
638}
639
640/**
641 * Encrypts a string using this source password algorithm.
642 * @param plainPassword the unencrypted password.
643 * @return a new encrypted string.
644 * @see _isPassword:equalTo:
645 */
646- (NSString *) _encryptPassword: (NSString *) thePassword
647{
648  NSString *pass;
649  pass = [thePassword asCryptedPassUsingScheme: _userPasswordAlgorithm
650                                       keyPath: nil];
651
652  if (pass == nil)
653    {
654      [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm];
655      return nil;
656    }
657
658  return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass];
659}
660
661- (BOOL)  _ldapModifyAttribute: (NSString *) theAttribute
662                     withValue: (NSString *) theValue
663                        userDN: (NSString *) theUserDN
664                      password: (NSString *) theUserPassword
665                    connection: (NGLdapConnection *) bindConnection
666{
667  NGLdapModification *mod;
668  NGLdapAttribute *attr;
669  NSArray *changes;
670
671  BOOL didChange;
672
673  attr = [[NGLdapAttribute alloc] initWithAttributeName: theAttribute];
674  [attr addStringValue: theValue];
675
676  mod = [NGLdapModification replaceModification: attr];
677
678  changes = [NSArray arrayWithObject: mod];
679
680  if ([bindConnection bindWithMethod: @"simple"
681                              binddn: theUserDN
682                         credentials: theUserPassword])
683    {
684      didChange = [bindConnection modifyEntryWithDN: theUserDN
685                                            changes: changes];
686    }
687  else
688    didChange = NO;
689
690  RELEASE(attr);
691
692  return didChange;
693}
694
695//
696//
697//
698- (BOOL) changePasswordForLogin: (NSString *) login
699                    oldPassword: (NSString *) oldPassword
700                    newPassword: (NSString *) newPassword
701                           perr: (SOGoPasswordPolicyError *) perr
702
703{
704  NGLdapConnection *bindConnection;
705  NSString *userDN;
706  BOOL didChange;
707
708  didChange = NO;
709
710  NS_DURING
711    if ([login length] > 0)
712      {
713        bindConnection = [[NGLdapConnection alloc] initWithHostName: _hostname
714                                                               port: _port];
715        if (![_encryption length] || [self _setupEncryption: bindConnection])
716          {
717            if (_queryTimeout > 0)
718              [bindConnection setQueryTimeLimit: _queryTimeout];
719            if (_bindFields)
720              userDN = [self _fetchUserDNForLogin: login];
721            else
722              {
723                userDN = [NSString stringWithFormat: @"%@=%@,%@",
724                                   _IDField, [login escapedForLDAPDN], _baseDN];
725              }
726            if (userDN)
727              {
728                if ([bindConnection isADCompatible])
729                  {
730                    if ([bindConnection bindWithMethod: @"simple"
731                                                binddn: userDN
732                                           credentials: oldPassword])
733                      {
734                        didChange = [bindConnection changeADPasswordAtDn: userDN
735                                                             oldPassword: oldPassword
736                                                             newPassword: newPassword];
737                      }
738                  }
739                else if (_passwordPolicy)
740                  {
741                    if ([bindConnection bindWithMethod: @"simple"
742                                                binddn: _sourceBindDN
743                                           credentials: _sourceBindPassword])
744                      {
745                        didChange = [bindConnection changePasswordAtDn: userDN
746                                                           oldPassword: oldPassword
747                                                           newPassword: newPassword
748                                                                  perr: (void *)perr];
749                      }
750                  }
751                else
752                  {
753                    // We don't use a password policy - we simply use
754                    // a modify-op to change the password
755                    NSString* encryptedPass;
756
757                    if ([_userPasswordAlgorithm isEqualToString: @"none"])
758                      {
759                        encryptedPass = newPassword;
760                      }
761                    else
762                      {
763                        encryptedPass = [self _encryptPassword: newPassword];
764                      }
765
766                    if (encryptedPass != nil)
767                      {
768                        if ([bindConnection bindWithMethod: @"simple"
769                                                    binddn: userDN
770                                               credentials: oldPassword])
771                          {
772                            didChange = [self _ldapModifyAttribute: @"userPassword"
773                                                         withValue: encryptedPass
774                                                            userDN: userDN
775                                                          password: oldPassword
776                                                        connection: bindConnection];
777                            if (didChange)
778                              {
779                                *perr = PolicyNoError;
780                              }
781                          }
782                      }
783                  }
784
785                // We must check if we must update the Samba NT/LM password hashes
786                if (didChange && _updateSambaNTLMPasswords)
787                  {
788                    [self _ldapModifyAttribute: @"sambaNTPassword"
789                                     withValue: [newPassword asNTHash]
790                                        userDN: userDN
791                                      password: newPassword
792                                    connection: bindConnection];
793
794                    [self _ldapModifyAttribute: @"sambaLMPassword"
795                                     withValue: [newPassword asLMHash]
796                                        userDN: userDN
797                                      password: newPassword
798                                    connection: bindConnection];
799                  }
800              }
801          }
802      }
803  NS_HANDLER
804    {
805      if ([[localException name] isEqual: @"LDAPException"] &&
806          ([[[localException userInfo] objectForKey: @"error_code"] intValue] == LDAP_CONSTRAINT_VIOLATION))
807        {
808          *perr = PolicyInsufficientPasswordQuality;
809        }
810      else
811        {
812          [self logWithFormat: @"%@", localException];
813        }
814    }
815  NS_ENDHANDLER ;
816
817  [bindConnection release];
818  return didChange;
819}
820
821
822/**
823 * Search for contacts matching some string.
824 * @param filter the string to search for
825 * @see fetchContactsMatching:
826 * @return a EOQualifier matching the filter
827 */
828- (EOQualifier *) _qualifierForFilter: (NSString *) filter
829                           onCriteria: (NSArray *) criteria
830{
831  NSEnumerator *criteriaList;
832  NSMutableArray *fields;
833  NSString *fieldFormat, *currentCriteria, *searchFormat, *escapedFilter;
834  EOQualifier *qualifier;
835  NSMutableString *qs;
836
837  escapedFilter = SafeLDAPCriteria(filter);
838  qs = [NSMutableString string];
839
840  if (([escapedFilter length] == 0 && !_listRequiresDot) || [escapedFilter isEqualToString: @"."])
841    {
842      [qs appendFormat: @"(%@='*')", _CNField];
843    }
844  else
845    {
846      fieldFormat = [NSString stringWithFormat: @"(%%@='*%@*')", escapedFilter];
847      if (criteria)
848        criteriaList = [criteria objectEnumerator];
849      else
850        criteriaList = [[self searchFields] objectEnumerator];
851
852      fields = [NSMutableArray array];
853      while (( currentCriteria = [criteriaList nextObject] ))
854        {
855          if ([currentCriteria isEqualToString: @"name"])
856            {
857              [fields addObject: @"sn"];
858              [fields addObject: @"displayname"];
859              [fields addObject: @"cn"];
860            }
861          else if ([currentCriteria isEqualToString: @"mail"])
862            {
863              // Expand to all mail fields
864              [fields addObject: currentCriteria];
865              [fields addObjectsFromArray: _mailFields];
866            }
867          else if ([[self searchFields] containsObject: currentCriteria])
868            [fields addObject: currentCriteria];
869        }
870
871      searchFormat = [[[fields uniqueObjects] stringsWithFormat: fieldFormat] componentsJoinedByString: @" OR "];
872      [qs appendString: searchFormat];
873    }
874
875  if (_filter && [_filter length])
876    [qs appendFormat: @" AND %@", _filter];
877
878  if ([qs length])
879    qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
880  else
881    qualifier = nil;
882
883  return qualifier;
884}
885
886- (EOQualifier *) _qualifierForUIDFilter: (NSString *) uid
887{
888  NSString *mailFormat, *fieldFormat, *escapedUid, *currentField;
889  NSEnumerator *bindFieldsEnum;
890  NSMutableString *qs;
891
892  escapedUid = SafeLDAPCriteria(uid);
893
894  fieldFormat = [NSString stringWithFormat: @"(%%@='%@')", escapedUid];
895  mailFormat = [[_mailFields stringsWithFormat: fieldFormat]
896                     componentsJoinedByString: @" OR "];
897  qs = [NSMutableString stringWithFormat: @"(%@='%@') OR %@",
898                        _UIDField, escapedUid, mailFormat];
899  if (_bindFields)
900    {
901      bindFieldsEnum = [_bindFields objectEnumerator];
902      while ((currentField = [bindFieldsEnum nextObject]))
903        {
904          if ([currentField caseInsensitiveCompare: _UIDField] != NSOrderedSame
905              && ![_mailFields containsObject: currentField])
906            [qs appendFormat: @" OR (%@='%@')", [currentField stringByTrimmingSpaces], escapedUid];
907        }
908    }
909
910  if (_filter && [_filter length])
911    [qs appendFormat: @" AND %@", _filter];
912
913  return [EOQualifier qualifierWithQualifierFormat: qs];
914}
915
916/*
917- (NSArray *) _constraintsFields
918{
919  NSMutableArray *fields;
920  NSEnumerator *values;
921  NSDictionary *currentConstraint;
922
923  fields = [NSMutableArray array];
924  values = [[modulesConstraints allValues] objectEnumerator];
925  while ((currentConstraint = [values nextObject]))
926    [fields addObjectsFromArray: [currentConstraint allKeys]];
927
928  return fields;
929}
930*/
931
932/* This is required for SQL sources when DomainFieldName is enabled.
933 * For LDAP, simply discard the domain and call the original method */
934- (NSArray *) allEntryIDsVisibleFromDomain: (NSString *) theDomain
935{
936  return [self allEntryIDs];
937}
938
939- (NSArray *) allEntryIDs
940{
941  NSEnumerator *entries;
942  NGLdapEntry *currentEntry;
943  NGLdapConnection *ldapConnection;
944  EOQualifier *qualifier;
945  NSMutableString *qs;
946  NSString *value;
947  NSArray *attributes;
948  NSMutableArray *ids;
949
950  ids = [NSMutableArray array];
951
952  ldapConnection = [self _ldapConnection];
953  attributes = [NSArray arrayWithObject: _IDField];
954
955  qs = [NSMutableString stringWithFormat: @"(%@='*')", _CNField];
956  if ([_filter length])
957    [qs appendFormat: @" AND %@", _filter];
958  qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
959
960  if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
961    entries = [ldapConnection baseSearchAtBaseDN: _baseDN
962                                       qualifier: qualifier
963                                      attributes: attributes];
964  else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
965    entries = [ldapConnection flatSearchAtBaseDN: _baseDN
966                                       qualifier: qualifier
967                                      attributes: attributes];
968  else
969    entries = [ldapConnection deepSearchAtBaseDN: _baseDN
970                                       qualifier: qualifier
971                                      attributes: attributes];
972
973  while ((currentEntry = [entries nextObject]))
974    {
975      value = [[currentEntry attributeWithName: _IDField]
976                            stringValueAtIndex: 0];
977      if ([value length] > 0)
978        [ids addObject: value];
979    }
980
981  return ids;
982}
983
984- (void) _fillEmailsOfEntry: (NGLdapEntry *) ldapEntry
985             intoLDIFRecord: (NSMutableDictionary *) ldifRecord
986{
987  NSString *currentFieldName, *ldapValue;
988  NSEnumerator *emailFields;
989  NSMutableArray *emails;
990  NSArray *allValues;
991
992  emails = [[NSMutableArray alloc] init];
993  emailFields = [_mailFields objectEnumerator];
994  while ((currentFieldName = [emailFields nextObject]))
995    {
996      allValues = [[ldapEntry attributeWithName: currentFieldName]
997                    allStringValues];
998
999      // Special case handling for Microsoft Active Directory. proxyAddresses
1000      // is generally prefixed with smtp: - if we find this (or any value preceeding
1001      // the semi-colon), we strip it. See https://msdn.microsoft.com/en-us/library/ms679424(v=vs.85).aspx
1002      if ([currentFieldName caseInsensitiveCompare: @"proxyAddresses"] == NSOrderedSame)
1003	{
1004	  NSRange r;
1005	  int i;
1006
1007	  for (i = 0; i < [allValues count]; i++)
1008	    {
1009	      ldapValue = [allValues objectAtIndex: i];
1010	      r = [ldapValue rangeOfString: @":"];
1011
1012	      if (r.length)
1013		{
1014		  // We only keep "smtp" ones
1015		  if ([[ldapValue lowercaseString] hasPrefix: @"smtp"])
1016		    [emails addObject: [ldapValue substringFromIndex: r.location+1]];
1017		}
1018	      else
1019		[emails addObject: ldapValue];
1020	    }
1021	}
1022      else
1023	[emails addObjectsFromArray: allValues];
1024    }
1025  [ldifRecord setObject: emails forKey: @"c_emails"];
1026  [emails release];
1027
1028  if (_IMAPHostField)
1029    {
1030      ldapValue = [[ldapEntry attributeWithName: _IMAPHostField] stringValueAtIndex: 0];
1031      if ([ldapValue length] > 0)
1032        [ldifRecord setObject: ldapValue forKey: @"c_imaphostname"];
1033    }
1034
1035  if (_IMAPLoginField)
1036    {
1037      ldapValue = [[ldapEntry attributeWithName: _IMAPLoginField] stringValueAtIndex: 0];
1038      if ([ldapValue length] > 0)
1039        [ldifRecord setObject: ldapValue forKey: @"c_imaplogin"];
1040    }
1041
1042  if (_SieveHostField)
1043    {
1044      ldapValue = [[ldapEntry attributeWithName: _SieveHostField] stringValueAtIndex: 0];
1045      if ([ldapValue length] > 0)
1046        [ldifRecord setObject: ldapValue forKey: @"c_sievehostname"];
1047    }
1048}
1049
1050- (void) _fillConstraints: (NGLdapEntry *) ldapEntry
1051                forModule: (NSString *) module
1052           intoLDIFRecord: (NSMutableDictionary *) ldifRecord
1053{
1054  NSDictionary *constraints;
1055  NSEnumerator *matches, *ldapValues;
1056  NSString *currentMatch, *currentValue, *ldapValue;
1057  BOOL result;
1058
1059  result = YES;
1060
1061  constraints = [_modulesConstraints objectForKey: module];
1062  if (constraints)
1063    {
1064      matches = [[constraints allKeys] objectEnumerator];
1065      while (result == YES && (currentMatch = [matches nextObject]))
1066        {
1067          ldapValues = [[[ldapEntry attributeWithName: currentMatch] allStringValues] objectEnumerator];
1068          currentValue = [constraints objectForKey: currentMatch];
1069          result = NO;
1070
1071          while (result == NO && (ldapValue = [ldapValues nextObject]))
1072            if ([ldapValue caseInsensitiveMatches: currentValue])
1073              result = YES;
1074        }
1075    }
1076
1077  [ldifRecord setObject: [NSNumber numberWithBool: result]
1078                 forKey: [NSString stringWithFormat: @"%@Access", module]];
1079}
1080
1081/* conversion LDAP -> SOGo inetOrgPerson entry */
1082- (void) applyContactMappingToResult: (NSMutableDictionary *) ldifRecord
1083{
1084  NSArray *sourceFields;
1085  NSArray *keys;
1086  NSString *key, *field, *value;
1087  NSUInteger count, max, fieldCount, fieldMax;
1088  BOOL filled;
1089
1090  keys = [_contactMapping allKeys];
1091  max = [keys count];
1092  for (count = 0; count < max; count++)
1093    {
1094      key = [keys objectAtIndex: count];
1095      sourceFields = [_contactMapping objectForKey: key];
1096      if ([sourceFields isKindOfClass: NSStringK])
1097        sourceFields = [NSArray arrayWithObject: sourceFields];
1098      fieldMax = [sourceFields count];
1099      filled = NO;
1100      for (fieldCount = 0;
1101           !filled && fieldCount < fieldMax;
1102           fieldCount++)
1103        {
1104          field = [[sourceFields objectAtIndex: fieldCount] lowercaseString];
1105          value = [ldifRecord objectForKey: field];
1106          if (value)
1107            {
1108              [ldifRecord setObject: value forKey: [key lowercaseString]];
1109              filled = YES;
1110            }
1111        }
1112    }
1113}
1114
1115/* conversion SOGo inetOrgPerson entry -> LDAP */
1116- (void) applyContactMappingToOutput: (NSMutableDictionary *) ldifRecord
1117{
1118  NSArray *sourceFields;
1119  NSArray *keys;
1120  NSString *key, *lowerKey, *field, *value;
1121  NSUInteger count, max, fieldCount, fieldMax;
1122
1123  if (_contactObjectClasses)
1124    [ldifRecord setObject: _contactObjectClasses
1125                   forKey: @"objectclass"];
1126
1127  keys = [_contactMapping allKeys];
1128  max = [keys count];
1129  for (count = 0; count < max; count++)
1130    {
1131      key = [keys objectAtIndex: count];
1132      lowerKey = [key lowercaseString];
1133      value = [ldifRecord objectForKey: lowerKey];
1134      if ([value length] > 0)
1135        {
1136          sourceFields = [_contactMapping objectForKey: key];
1137          if ([sourceFields isKindOfClass: NSStringK])
1138            sourceFields = [NSArray arrayWithObject: sourceFields];
1139
1140          fieldMax = [sourceFields count];
1141          for (fieldCount = 0; fieldCount < fieldMax; fieldCount++)
1142            {
1143              field = [[sourceFields objectAtIndex: fieldCount]
1144                        lowercaseString];
1145              [ldifRecord setObject: value forKey: field];
1146            }
1147        }
1148    }
1149}
1150
1151- (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry
1152{
1153  NSMutableDictionary *ldifRecord;
1154  NSString *value;
1155  static NSArray *resourceKinds = nil;
1156  NSMutableArray *classes;
1157  NSEnumerator *gclasses;
1158  NSString *gclass;
1159  id o;
1160
1161  if (!resourceKinds)
1162    resourceKinds = [[NSArray alloc] initWithObjects: @"location", @"thing",
1163                                     @"group", nil];
1164
1165  ldifRecord = [ldapEntry asDictionary];
1166  [ldifRecord setObject: self forKey: @"source"];
1167  [ldifRecord setObject: [ldapEntry dn] forKey: @"dn"];
1168
1169  // We get our objectClass attribute values. We lowercase
1170  // everything for ease of search after.
1171  o = [ldapEntry objectClasses];
1172  classes = nil;
1173
1174  if (o)
1175    {
1176      int i, c;
1177
1178      classes = [NSMutableArray arrayWithArray: o];
1179      c = [classes count];
1180      for (i = 0; i < c; i++)
1181        [classes replaceObjectAtIndex: i
1182          withObject: [[classes objectAtIndex: i] lowercaseString]];
1183    }
1184
1185  if (classes)
1186    {
1187      // We check if our entry is a resource. We also support
1188      // determining resources based on the KindFieldName attribute
1189      // value - see below.
1190      if ([classes containsObject: @"calendarresource"])
1191        {
1192          [ldifRecord setObject: [NSNumber numberWithInt: 1]
1193                         forKey: @"isResource"];
1194        }
1195      else
1196        {
1197        // We check if our entry is a group. If so, we set the
1198        // 'isGroup' custom attribute.
1199        gclasses = [_groupObjectClasses objectEnumerator];
1200        while ((gclass = [gclasses nextObject]))
1201         if ([classes containsObject: [gclass lowercaseString]])
1202           {
1203             [ldifRecord setObject: [NSNumber numberWithInt: 1]
1204                            forKey: @"isGroup"];
1205             break;
1206           }
1207        }
1208    }
1209
1210  // We check if that entry corresponds to a resource. For this,
1211  // kindField must be defined and it must hold one of those values
1212  //
1213  // location
1214  // thing
1215  // group
1216  //
1217  if ([_kindField length] > 0)
1218    {
1219      value = [ldifRecord objectForKey: [_kindField lowercaseString]];
1220      if ([value isKindOfClass: NSStringK]
1221          && [resourceKinds containsObject: value])
1222        [ldifRecord setObject: [NSNumber numberWithInt: 1]
1223                       forKey: @"isResource"];
1224    }
1225
1226  // We check for the number of simultanous bookings that is allowed.
1227  // A value of 0 means that there's no limit.
1228  if ([_multipleBookingsField length] > 0)
1229    {
1230      value = [ldifRecord objectForKey: [_multipleBookingsField lowercaseString]];
1231      [ldifRecord setObject: [NSNumber numberWithInt: [value intValue]]
1232                     forKey: @"numberOfSimultaneousBookings"];
1233    }
1234
1235  value = [[ldapEntry attributeWithName: _IDField] stringValueAtIndex: 0];
1236  if (!value)
1237    value = @"";
1238  [ldifRecord setObject: value forKey: @"c_name"];
1239  value = [[ldapEntry attributeWithName: _UIDField] stringValueAtIndex: 0];
1240  if (!value)
1241    value = @"";
1242//  else
1243//    {
1244//      Eventually, we could check at this point if the entry is a group
1245//      and prefix the UID with a "@"
1246//    }
1247  [ldifRecord setObject: value forKey: @"c_uid"];
1248  value = [[ldapEntry attributeWithName: _CNField] stringValueAtIndex: 0];
1249  if (!value)
1250    value = @"";
1251  [ldifRecord setObject: value forKey: @"c_cn"];
1252  /* if "displayName" is not set, we use CNField because it must exist */
1253  if (![ldifRecord objectForKey: @"displayname"])
1254    [ldifRecord setObject: value forKey: @"displayname"];
1255
1256  if (_contactInfoAttribute)
1257    {
1258      value = [[ldapEntry attributeWithName: _contactInfoAttribute]
1259                stringValueAtIndex: 0];
1260      if (!value)
1261        value = @"";
1262    }
1263  else
1264    value = @"";
1265  [ldifRecord setObject: value forKey: @"c_info"];
1266
1267  if (_domain)
1268    value = _domain;
1269  else
1270    value = @"";
1271  [ldifRecord setObject: value forKey: @"c_domain"];
1272
1273  [self _fillEmailsOfEntry: ldapEntry intoLDIFRecord: ldifRecord];
1274  [self _fillConstraints: ldapEntry forModule: @"Calendar"
1275          intoLDIFRecord: (NSMutableDictionary *) ldifRecord];
1276  [self _fillConstraints: ldapEntry forModule: @"Mail"
1277          intoLDIFRecord: (NSMutableDictionary *) ldifRecord];
1278  [self _fillConstraints: ldapEntry forModule: @"ActiveSync"
1279          intoLDIFRecord: (NSMutableDictionary *) ldifRecord];
1280
1281  if (_contactMapping)
1282    [self applyContactMappingToResult: ldifRecord];
1283
1284  return ldifRecord;
1285}
1286
1287- (NSArray *) fetchContactsMatching: (NSString *) match
1288                       withCriteria: (NSArray *) criteria
1289                           inDomain: (NSString *) theDomain
1290{
1291  NSAutoreleasePool *pool;
1292  NGLdapConnection *ldapConnection;
1293  NGLdapEntry *currentEntry;
1294  NSEnumerator *entries;
1295  NSMutableArray *contacts;
1296  EOQualifier *qualifier;
1297  unsigned int i;
1298
1299  contacts = [NSMutableArray array];
1300
1301  if ([match length] > 0 || !_listRequiresDot)
1302    {
1303      ldapConnection = [self _ldapConnection];
1304      qualifier = [self _qualifierForFilter: match onCriteria: criteria];
1305
1306      if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
1307        entries = [ldapConnection baseSearchAtBaseDN: _baseDN
1308                                           qualifier: qualifier
1309                                          attributes: _lookupFields];
1310      else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
1311        entries = [ldapConnection flatSearchAtBaseDN: _baseDN
1312                                           qualifier: qualifier
1313                                          attributes: _lookupFields];
1314      else /* we do it like before */
1315        entries = [ldapConnection deepSearchAtBaseDN: _baseDN
1316                                           qualifier: qualifier
1317                                          attributes: _lookupFields];
1318
1319      i = 0;
1320      pool = [NSAutoreleasePool new];
1321      while ((currentEntry = [entries nextObject]))
1322        {
1323          [contacts addObject:
1324                      [self _convertLDAPEntryToContact: currentEntry]];
1325          i++;
1326          if (i % 10 == 0)
1327            {
1328              [pool release];
1329              pool = [NSAutoreleasePool new];
1330            }
1331        }
1332      [pool release];
1333    }
1334
1335  return contacts;
1336}
1337
1338- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) theQualifier
1339		   usingConnection: (id) connection
1340{
1341  NGLdapConnection *ldapConnection;
1342  NSEnumerator *entries;
1343
1344  ldapConnection = (NGLdapConnection *)connection;
1345
1346  if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
1347    entries = [ldapConnection baseSearchAtBaseDN: _baseDN
1348                                       qualifier: theQualifier
1349                                      attributes: _lookupFields];
1350  else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
1351    entries = [ldapConnection flatSearchAtBaseDN: _baseDN
1352                                       qualifier: theQualifier
1353                                      attributes: _lookupFields];
1354  else
1355    entries = [ldapConnection deepSearchAtBaseDN: _baseDN
1356                                       qualifier: theQualifier
1357                                      attributes: _lookupFields];
1358
1359  return [entries nextObject];
1360}
1361
1362- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) theQualifier
1363{
1364  return [self _lookupLDAPEntry: theQualifier
1365		usingConnection: [self _ldapConnection]];
1366}
1367
1368- (NSDictionary *) lookupContactEntry: (NSString *) theID
1369                             inDomain: (NSString *) theDomain
1370		      usingConnection: (id) connection
1371{
1372  NGLdapEntry *ldapEntry;
1373  EOQualifier *qualifier;
1374  NSString *s;
1375  NSDictionary *ldifRecord;
1376
1377  ldifRecord = nil;
1378
1379  if ([theID length] > 0)
1380    {
1381      s = [NSString stringWithFormat: @"(%@='%@')",
1382                    _IDField, SafeLDAPCriteria(theID)];
1383      qualifier = [EOQualifier qualifierWithQualifierFormat: s];
1384      ldapEntry = [self _lookupLDAPEntry: qualifier
1385			 usingConnection: connection];
1386      if (ldapEntry)
1387        ldifRecord = [self _convertLDAPEntryToContact: ldapEntry];
1388    }
1389
1390  return ldifRecord;
1391}
1392
1393- (NSDictionary *) lookupContactEntry: (NSString *) theID
1394                             inDomain: (NSString *) theDomain
1395{
1396  return [self lookupContactEntry: theID
1397			 inDomain: theDomain
1398		  usingConnection: [self _ldapConnection]];
1399}
1400
1401- (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) uid
1402                                           inDomain: (NSString *) theDomain
1403{
1404  NGLdapEntry *ldapEntry;
1405  EOQualifier *qualifier;
1406  NSDictionary *ldifRecord;
1407
1408  ldifRecord = nil;
1409
1410  if ([uid length] > 0)
1411    {
1412      qualifier = [self _qualifierForUIDFilter: uid];
1413      ldapEntry = [self _lookupLDAPEntry: qualifier];
1414      if (ldapEntry)
1415        ldifRecord = [self _convertLDAPEntryToContact: ldapEntry];
1416    }
1417
1418  return ldifRecord;
1419}
1420
1421- (NSString *) lookupLoginByDN: (NSString *) theDN
1422{
1423  NGLdapConnection *ldapConnection;
1424  NGLdapEntry *entry;
1425  EOQualifier *qualifier;
1426  NSString *login;
1427
1428  login = nil;
1429  qualifier = nil;
1430
1431  ldapConnection = [self _ldapConnection];
1432
1433  if (_filter)
1434    qualifier = [EOQualifier qualifierWithQualifierFormat: _filter];
1435
1436  entry = [ldapConnection entryAtDN: theDN
1437			  qualifier: qualifier
1438                         attributes: [NSArray arrayWithObject: _UIDField]];
1439  if (entry)
1440    login = [[entry attributeWithName: _UIDField] stringValueAtIndex: 0];
1441
1442  return login;
1443}
1444
1445- (NSDictionary *) lookupContactEntryByDN: (NSString *) theDN
1446{
1447  NGLdapConnection *ldapConnection;
1448  NGLdapEntry *ldapEntry;
1449  EOQualifier *qualifier;
1450  NSDictionary *ldifRecord;
1451
1452  ldifRecord = nil;
1453  qualifier = nil;
1454
1455  ldapConnection = [self _ldapConnection];
1456
1457  if (_filter)
1458    qualifier = [EOQualifier qualifierWithQualifierFormat: _filter];
1459
1460  ldapEntry = [ldapConnection entryAtDN: theDN
1461                              qualifier: qualifier
1462                             attributes: [NSArray arrayWithObject: @"*"]];
1463  if (ldapEntry)
1464    ldifRecord = [self _convertLDAPEntryToContact: ldapEntry];
1465
1466  return ldifRecord;
1467}
1468
1469- (NSString *) lookupDNByLogin: (NSString *) theLogin
1470{
1471  return [[SOGoCache sharedCache] distinguishedNameForLogin: theLogin];
1472}
1473
1474- (NGLdapEntry *) _lookupGroupEntryByAttributes: (NSArray *) theAttributes
1475                                       andValue: (NSString *) theValue
1476{
1477  EOQualifier *qualifier;
1478  NGLdapEntry *ldapEntry;
1479  NSString *s;
1480
1481  if ([theValue length] > 0 && [theAttributes count] > 0)
1482    {
1483      if ([theAttributes count] == 1)
1484	{
1485	    s = [NSString stringWithFormat: @"(%@='%@')",
1486			  [theAttributes lastObject], SafeLDAPCriteria(theValue)];
1487
1488	}
1489      else
1490	{
1491	  NSString *fieldFormat;
1492
1493	  fieldFormat = [NSString stringWithFormat: @"(%%@='%@')", SafeLDAPCriteria(theValue)];
1494	  s = [[theAttributes stringsWithFormat: fieldFormat]
1495			 componentsJoinedByString: @" OR "];
1496	}
1497
1498      qualifier = [EOQualifier qualifierWithQualifierFormat: s];
1499      ldapEntry = [self _lookupLDAPEntry: qualifier];
1500    }
1501  else
1502    ldapEntry = nil;
1503
1504  return ldapEntry;
1505}
1506
1507- (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID
1508                               inDomain: (NSString *) theDomain
1509{
1510  return [self _lookupGroupEntryByAttributes: [NSArray arrayWithObject: _UIDField]
1511				    andValue: theUID];
1512}
1513
1514- (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail
1515                                 inDomain: (NSString *) theDomain
1516{
1517  return [self _lookupGroupEntryByAttributes: _mailFields
1518				    andValue: theEmail];
1519}
1520
1521- (void) setSourceID: (NSString *) newSourceID
1522{
1523  ASSIGN(_sourceID, newSourceID);
1524}
1525
1526- (NSString *) sourceID
1527{
1528  return _sourceID;
1529}
1530
1531- (void) setDisplayName: (NSString *) newDisplayName
1532{
1533  ASSIGN(_displayName, newDisplayName);
1534}
1535
1536- (NSString *) displayName
1537{
1538  return _displayName;
1539}
1540
1541- (NSString *) baseDN
1542{
1543  return _baseDN;
1544}
1545
1546- (NSString *) MSExchangeHostname
1547{
1548  return _MSExchangeHostname;
1549}
1550
1551- (void) setModifiers: (NSArray *) newModifiers
1552{
1553  ASSIGN(_modifiers, newModifiers);
1554}
1555
1556- (NSArray *) modifiers
1557{
1558  return _modifiers;
1559}
1560
1561- (NSArray *) groupObjectClasses
1562{
1563  return _groupObjectClasses;
1564}
1565
1566- (BOOL) groupExpansionEnabled
1567{
1568  return _groupExpansionEnabled;
1569}
1570
1571static NSArray *
1572_convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifRecord)
1573{
1574  /* convert resulting record to NGLdapEntry:
1575     - strip non-existing object classes
1576     - ignore fields with empty values
1577     - ignore extra fields
1578     - use correct case for LDAP attribute matching classes */
1579  NSMutableArray *validClasses, *validFields, *attributes;
1580  NGLdapAttribute *attribute;
1581  NSArray *classes, *fields, *values;
1582  NSString *objectClass, *field, *lowerField, *value;
1583  NSUInteger count, max, valueCount, valueMax;
1584
1585  classes = [ldifRecord objectForKey: @"objectclass"];
1586  if ([classes isKindOfClass: NSStringK])
1587    classes = [NSArray arrayWithObject: classes];
1588  max = [classes count];
1589  validClasses = [NSMutableArray array];
1590  validFields = [NSMutableArray array];
1591  for (count = 0; count < max; count++)
1592    {
1593      objectClass = [classes objectAtIndex: count];
1594      fields = [schema fieldsForClass: objectClass];
1595      if ([fields count] > 0)
1596        {
1597          [validClasses addObject: objectClass];
1598          [validFields addObjectsFromArray: fields];
1599        }
1600    }
1601  [validFields removeDoubles];
1602
1603  attributes = [NSMutableArray new];
1604  max = [validFields count];
1605  for (count = 0; count < max; count++)
1606    {
1607      attribute = nil;
1608      field = [validFields objectAtIndex: count];
1609      lowerField = [field lowercaseString];
1610      if (![lowerField isEqualToString: @"dn"])
1611        {
1612          if ([lowerField isEqualToString: @"objectclass"])
1613            values = validClasses;
1614          else
1615            {
1616              values = [ldifRecord objectForKey: lowerField];
1617              if ([values isKindOfClass: NSStringK])
1618                values = [NSArray arrayWithObject: values];
1619            }
1620          valueMax = [values count];
1621          for (valueCount = 0; valueCount < valueMax; valueCount++)
1622            {
1623              value = [values objectAtIndex: valueCount];
1624              if ([value length] > 0)
1625                {
1626                  if (!attribute)
1627                    {
1628                      attribute = [[NGLdapAttribute alloc]
1629                                    initWithAttributeName: field];
1630                      [attributes addObject: attribute];
1631                      [attribute release];
1632                    }
1633                  [attribute addStringValue: value];
1634                }
1635            }
1636        }
1637    }
1638
1639  return attributes;
1640}
1641
1642- (NSException *) addContactEntry: (NSDictionary *) theEntry
1643                           withID: (NSString *) theId
1644{
1645  NGLdapConnection *ldapConnection;
1646  NSMutableDictionary *ldifRecord;
1647  NSString *dn, *cnValue;
1648  NGLdapEntry *newEntry;
1649  NSException *result;
1650  NSArray *attributes;
1651
1652  result = nil;
1653
1654  if ([theId length] > 0)
1655    {
1656      ldapConnection = [self _ldapConnection];
1657      ldifRecord = [theEntry mutableCopy];
1658      [ldifRecord autorelease];
1659      [ldifRecord setObject: theId forKey: _UIDField];
1660
1661      /* if CN is not set, we use aId because it must exist */
1662      if (![ldifRecord objectForKey: _CNField])
1663        {
1664          cnValue = [ldifRecord objectForKey: @"displayname"];
1665          if ([cnValue length] == 0)
1666            cnValue = theId;
1667          [ldifRecord setObject: theId forKey: @"cn"];
1668        }
1669
1670      [self applyContactMappingToOutput: ldifRecord];
1671
1672      /* since the id might have changed due to the mapping above, we
1673         reload the record ID */
1674      theId = [ldifRecord objectForKey: _UIDField];
1675      dn = [NSString stringWithFormat: @"%@=%@,%@", _IDField,
1676                     [theId escapedForLDAPDN], _baseDN];
1677      attributes = _convertRecordToLDAPAttributes(_schema, ldifRecord);
1678
1679      newEntry = [[NGLdapEntry alloc] initWithDN: dn
1680                                      attributes: attributes];
1681      [newEntry autorelease];
1682      [attributes release];
1683      NS_DURING
1684        {
1685          [ldapConnection addEntry: newEntry];
1686          result = nil;
1687        }
1688      NS_HANDLER
1689        {
1690          result = localException;
1691          [result retain];
1692        }
1693      NS_ENDHANDLER;
1694      [result autorelease];
1695    }
1696  else
1697    [self errorWithFormat: @"no value for id field '%@'", _IDField];
1698
1699  return result;
1700}
1701
1702static NSArray *
1703_makeLDAPChanges (NGLdapConnection *ldapConnection,
1704                  NSString *dn, NSArray *attributes)
1705{
1706  NSMutableArray *changes, *attributeNames, *origAttributeNames;
1707  NGLdapAttribute *attribute, *origAttribute;
1708  NSDictionary *origAttributes;
1709  NGLdapEntry *origEntry;
1710  NSString *name;
1711
1712  NSUInteger count, max;
1713
1714  /* additions and modifications */
1715  origEntry = [ldapConnection entryAtDN: dn
1716                             attributes: [NSArray arrayWithObject: @"*"]];
1717  origAttributes = [origEntry attributes];
1718
1719  max = [attributes count];
1720  changes = [NSMutableArray arrayWithCapacity: max];
1721  attributeNames = [NSMutableArray arrayWithCapacity: max];
1722  for (count = 0; count < max; count++)
1723    {
1724      attribute = [attributes objectAtIndex: count];
1725      name = [attribute attributeName];
1726      [attributeNames addObject: name];
1727      origAttribute = [origAttributes objectForKey: name];
1728      if (origAttribute)
1729        {
1730          if (![origAttribute isEqual: attribute])
1731            [changes
1732              addObject: [NGLdapModification replaceModification: attribute]];
1733        }
1734      else
1735        [changes addObject: [NGLdapModification addModification: attribute]];
1736    }
1737
1738  /* deletions */
1739  origAttributeNames = [[origAttributes allKeys] mutableCopy];
1740  [origAttributeNames autorelease];
1741  [origAttributeNames removeObjectsInArray: attributeNames];
1742  max = [origAttributeNames count];
1743  for (count = 0; count < max; count++)
1744    {
1745      name = [origAttributeNames objectAtIndex: count];
1746      origAttribute = [origAttributes objectForKey: name];
1747      /* the attribute must only have string values, otherwise it will anyway
1748         be missing from the new record */
1749      // allStrings = YES;
1750      // values = [origAttribute allValues];
1751      // valueMax = [values count];
1752      // for (valueCount = 0; allStrings && valueCount < valueMax; valueCount++)
1753      //   if (![[values objectAtIndex: valueCount] isKindOfClass: NSStringK])
1754      //     allStrings = NO;
1755      // if (allStrings)
1756      [changes
1757        addObject: [NGLdapModification deleteModification: origAttribute]];
1758    }
1759
1760  return changes;
1761}
1762
1763- (NSException *) updateContactEntry: (NSDictionary *) theEntry
1764{
1765  NGLdapConnection *ldapConnection;
1766  NSMutableDictionary *ldifRecord;
1767  NSArray *attributes, *changes;
1768  NSException *result;
1769  NSString *dn;
1770
1771  dn = [theEntry objectForKey: @"dn"];
1772  result = nil;
1773
1774  if ([dn length] > 0)
1775    {
1776      ldapConnection = [self _ldapConnection];
1777      ldifRecord = [theEntry mutableCopy];
1778      [ldifRecord autorelease];
1779      [self applyContactMappingToOutput: ldifRecord];
1780      attributes = _convertRecordToLDAPAttributes(_schema, ldifRecord);
1781
1782      changes = _makeLDAPChanges (ldapConnection, dn, attributes);
1783
1784      NS_DURING
1785        {
1786          [ldapConnection modifyEntryWithDN: dn
1787                                    changes: changes];
1788          result = nil;
1789        }
1790      NS_HANDLER
1791        {
1792          result = localException;
1793          [result retain];
1794        }
1795      NS_ENDHANDLER;
1796      [result autorelease];
1797    }
1798  else
1799    [self errorWithFormat: @"expected dn for modified record"];
1800
1801  return result;
1802}
1803
1804- (NSException *) removeContactEntryWithID: (NSString *) theId
1805{
1806  NGLdapConnection *ldapConnection;
1807  NSException *result;
1808  NSString *dn;
1809
1810  ldapConnection = [self _ldapConnection];
1811  dn = [NSString stringWithFormat: @"%@=%@,%@", _IDField,
1812                 [theId escapedForLDAPDN], _baseDN];
1813  NS_DURING
1814    {
1815      [ldapConnection removeEntryWithDN: dn];
1816      result = nil;
1817    }
1818  NS_HANDLER
1819    {
1820      result = localException;
1821      [result retain];
1822    }
1823  NS_ENDHANDLER;
1824
1825  [result autorelease];
1826
1827  return result;
1828}
1829
1830/* user addressbooks */
1831- (BOOL) hasUserAddressBooks
1832{
1833  return ([_abOU length] > 0);
1834}
1835
1836- (NSArray *) addressBookSourcesForUser: (NSString *) theUser
1837{
1838  NGLdapConnection *ldapConnection;
1839  NSMutableDictionary *entryRecord;
1840  NSArray *attributes, *modifier;
1841  NSMutableArray *sources;
1842  NSDictionary *sourceRec;
1843  NSEnumerator *entries;
1844  NGLdapEntry *entry;
1845  NSString *abBaseDN;
1846  LDAPSource *ab;
1847
1848  if ([self hasUserAddressBooks])
1849    {
1850      /* list subentries */
1851      sources = [NSMutableArray array];
1852
1853      ldapConnection = [self _ldapConnection];
1854      abBaseDN = [NSString stringWithFormat: @"ou=%@,%@=%@,%@",
1855                           [_abOU escapedForLDAPDN], _IDField,
1856                           [theUser escapedForLDAPDN], _baseDN];
1857
1858      /* test ou=addressbooks entry */
1859      attributes = [NSArray arrayWithObject: @"*"];
1860      entries = [ldapConnection baseSearchAtBaseDN: abBaseDN
1861                                         qualifier: nil
1862                                        attributes: attributes];
1863      entry = [entries nextObject];
1864      if (entry)
1865        {
1866          attributes = [NSArray arrayWithObjects: @"ou", @"description", nil];
1867          entries = [ldapConnection flatSearchAtBaseDN: abBaseDN
1868                                             qualifier: nil
1869                                            attributes: attributes];
1870          modifier = [NSArray arrayWithObject: theUser];
1871          while ((entry = [entries nextObject]))
1872            {
1873              sourceRec = [entry asDictionary];
1874              ab = [LDAPSource new];
1875              [ab setSourceID: [sourceRec objectForKey: @"ou"]];
1876              [ab setDisplayName: [sourceRec objectForKey: @"description"]];
1877              [ab setBindDN: _bindDN
1878		   password: _password
1879		   hostname: _hostname
1880		       port: [NSString stringWithFormat: @"%d", _port]
1881		 encryption: _encryption
1882		  bindAsCurrentUser: [NSString stringWithFormat: @"%d", NO]];
1883              [ab setBaseDN: [entry dn]
1884		    IDField: @"cn"
1885		    CNField: @"displayName"
1886		   UIDField: @"cn"
1887		 mailFields: nil
1888		  searchFields: nil
1889		  groupObjectClasses: nil
1890		  IMAPHostField: nil
1891		  IMAPLoginField: nil
1892		  SieveHostField: nil
1893		 bindFields: nil
1894		lookupFields: nil
1895		  kindField: nil
1896		  andMultipleBookingsField: nil];
1897              [ab setListRequiresDot: NO];
1898              [ab setModifiers: modifier];
1899              [sources addObject: ab];
1900              [ab release];
1901            }
1902        }
1903      else
1904        {
1905          entryRecord = [NSMutableDictionary dictionary];
1906          [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"];
1907          [entryRecord setObject: @"addressbooks" forKey: @"ou"];
1908          attributes = _convertRecordToLDAPAttributes(_schema, entryRecord);
1909          entry = [[NGLdapEntry alloc] initWithDN: abBaseDN
1910                                       attributes: attributes];
1911          [entry autorelease];
1912          [attributes release];
1913          NS_DURING
1914            {
1915              [ldapConnection addEntry: entry];
1916            }
1917          NS_HANDLER
1918            {
1919              [self errorWithFormat: @"failed to create ou=addressbooks"
1920                    @" entry for user"];
1921            }
1922          NS_ENDHANDLER;
1923        }
1924    }
1925  else
1926    sources = nil;
1927
1928  return sources;
1929}
1930
1931- (NSException *) addAddressBookSource: (NSString *) newId
1932                       withDisplayName: (NSString *) newDisplayName
1933                               forUser: (NSString *) user
1934{
1935  NSException *result;
1936  NSString *abDN;
1937  NGLdapConnection *ldapConnection;
1938  NSArray *attributes;
1939  NGLdapEntry *entry;
1940  NSMutableDictionary *entryRecord;
1941
1942  if ([self hasUserAddressBooks])
1943    {
1944      abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@",
1945                       [newId escapedForLDAPDN], [_abOU escapedForLDAPDN],
1946                       _IDField, [user escapedForLDAPDN], _baseDN];
1947      entryRecord = [NSMutableDictionary dictionary];
1948      [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"];
1949      [entryRecord setObject: newId forKey: @"ou"];
1950      if ([newDisplayName length] > 0)
1951        [entryRecord setObject: newDisplayName forKey: @"description"];
1952      ldapConnection = [self _ldapConnection];
1953      attributes = _convertRecordToLDAPAttributes(_schema, entryRecord);
1954      entry = [[NGLdapEntry alloc] initWithDN: abDN
1955                                   attributes: attributes];
1956      [entry autorelease];
1957      [attributes release];
1958      NS_DURING
1959        {
1960          [ldapConnection addEntry: entry];
1961          result = nil;
1962        }
1963      NS_HANDLER
1964        {
1965          [self errorWithFormat: @"failed to create addressbook entry"];
1966          result = localException;
1967          [result retain];
1968        }
1969      NS_ENDHANDLER;
1970      [result autorelease];
1971    }
1972  else
1973    result = [NSException exceptionWithName: @"LDAPSourceIOException"
1974                                     reason: @"user addressbooks"
1975                          @" are not supported"
1976                                   userInfo: nil];
1977
1978  return result;
1979}
1980
1981- (NSException *) renameAddressBookSource: (NSString *) newId
1982                          withDisplayName: (NSString *) newDisplayName
1983                                  forUser: (NSString *) user
1984{
1985  NGLdapConnection *ldapConnection;
1986  NSMutableDictionary *entryRecord;
1987  NSArray *attributes, *changes;
1988  NSException *result;
1989  NSString *abDN;
1990
1991  if ([self hasUserAddressBooks])
1992    {
1993      abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@",
1994                       [newId escapedForLDAPDN], [_abOU escapedForLDAPDN],
1995                       _IDField, [user escapedForLDAPDN], _baseDN];
1996      entryRecord = [NSMutableDictionary dictionary];
1997      [entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"];
1998      [entryRecord setObject: newId forKey: @"ou"];
1999      if ([newDisplayName length] > 0)
2000        [entryRecord setObject: newDisplayName forKey: @"description"];
2001      ldapConnection = [self _ldapConnection];
2002      attributes = _convertRecordToLDAPAttributes(_schema, entryRecord);
2003      changes = _makeLDAPChanges (ldapConnection, abDN, attributes);
2004      [attributes release];
2005      NS_DURING
2006        {
2007          [ldapConnection modifyEntryWithDN: abDN
2008                                    changes: changes];
2009          result = nil;
2010        }
2011      NS_HANDLER
2012        {
2013          [self errorWithFormat: @"failed to rename addressbook entry"];
2014          result = localException;
2015          [result retain];
2016        }
2017      NS_ENDHANDLER;
2018      [result autorelease];
2019    }
2020  else
2021    result = [NSException exceptionWithName: @"LDAPSourceIOException"
2022                                     reason: @"user addressbooks"
2023                          @" are not supported"
2024                                   userInfo: nil];
2025
2026  return result;
2027}
2028
2029- (NSException *) removeAddressBookSource: (NSString *) newId
2030                                  forUser: (NSString *) user
2031{
2032  NGLdapConnection *ldapConnection;
2033  NSEnumerator *entries;
2034  NSException *result;
2035  NGLdapEntry *entry;
2036  NSString *abDN;
2037
2038  if ([self hasUserAddressBooks])
2039    {
2040      abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@",
2041                       [newId escapedForLDAPDN], [_abOU escapedForLDAPDN],
2042                       _IDField, [user escapedForLDAPDN], _baseDN];
2043      ldapConnection = [self _ldapConnection];
2044      NS_DURING
2045        {
2046          /* we must remove the ab sub=entries prior to the ab entry */
2047          entries = [ldapConnection flatSearchAtBaseDN: abDN
2048                                             qualifier: nil
2049                                            attributes: nil];
2050          while ((entry = [entries nextObject]))
2051            [ldapConnection removeEntryWithDN: [entry dn]];
2052          [ldapConnection removeEntryWithDN: abDN];
2053          result = nil;
2054        }
2055      NS_HANDLER
2056        {
2057          [self errorWithFormat: @"failed to remove addressbook entry"];
2058          result = localException;
2059          [result retain];
2060        }
2061      NS_ENDHANDLER;
2062      [result autorelease];
2063    }
2064  else
2065    result = [NSException exceptionWithName: @"LDAPSourceIOException"
2066                                     reason: @"user addressbooks"
2067                          @" are not supported"
2068                                   userInfo: nil];
2069
2070  return result;
2071}
2072
2073- (void) updateBaseDNFromLogin: (NSString *) theLogin
2074{
2075  NSMutableString *s;
2076  NSRange r;
2077
2078  r = [theLogin rangeOfString: @"@"];
2079  if (r.location != NSNotFound &&
2080      [_pristineBaseDN rangeOfString: @"%d"].location != NSNotFound)
2081    {
2082      s = [NSMutableString stringWithString: _pristineBaseDN];
2083      [s replaceOccurrencesOfString: @"%d"  withString: [theLogin substringFromIndex: r.location+1]  options: 0  range: NSMakeRange(0, [s length])];
2084      ASSIGN(_baseDN, s);
2085    }
2086}
2087
2088#define CHECK_CLASS(o) ({ \
2089  if ([o isKindOfClass: [NSString class]]) \
2090    o = [NSArray arrayWithObject: o]; \
2091})
2092
2093- (NSArray *) membersForGroupWithUID: (NSString *) uid
2094{
2095  NSMutableArray *dns, *uids;
2096  NSString *dn, *login;
2097  SOGoUserManager *um;
2098  NSDictionary *d, *contactInfos;
2099  SOGoUser *user;
2100  NSArray *o, *subusers, *logins;
2101  NSAutoreleasePool *pool;
2102  int i, c;
2103  NGLdapEntry *entry;
2104  NSMutableArray *members = nil;
2105
2106  if ([uid hasPrefix: @"@"])
2107    uid = [uid substringFromIndex: 1];
2108
2109  entry = [self lookupGroupEntryByUID: uid  inDomain: nil];
2110
2111  if (entry)
2112    {
2113      members = [NSMutableArray new];
2114      uids = [NSMutableArray array];
2115      dns = [NSMutableArray array];
2116
2117      // We check if it's a static group
2118      // Fetch "members" - we get DNs
2119      d = [entry asDictionary];
2120      o = [d objectForKey: @"member"];
2121      CHECK_CLASS(o);
2122      if (o) [dns addObjectsFromArray: o];
2123
2124      // Fetch "uniqueMembers" - we get DNs
2125      o = [d objectForKey: @"uniquemember"];
2126      CHECK_CLASS(o);
2127      if (o) [dns addObjectsFromArray: o];
2128
2129      // Fetch "memberUid" - we get UID (like login names)
2130      o = [d objectForKey: @"memberuid"];
2131      CHECK_CLASS(o);
2132      if (o) [uids addObjectsFromArray: o];
2133
2134      c = [dns count] + [uids count];
2135
2136      // We deal with a static group, let's add the members
2137      if (c)
2138        {
2139          um = [SOGoUserManager sharedUserManager];
2140
2141          // We add members for whom we have their associated DN
2142          for (i = 0; i < [dns count]; i++)
2143            {
2144              pool = [NSAutoreleasePool new];
2145              dn = [dns objectAtIndex: i];
2146              login = [um getLoginForDN: [dn lowercaseString]];
2147              user = [SOGoUser userWithLogin: login  roles: nil];
2148              if (user)
2149                {
2150                  contactInfos = [self lookupContactEntryWithUIDorEmail: login inDomain: nil];
2151                  if ([contactInfos objectForKey: @"isGroup"])
2152                    {
2153                      subusers = [self membersForGroupWithUID: login];
2154                      [members addObjectsFromArray: subusers];
2155                    }
2156                  else
2157                    {
2158                      [members addObject: user];
2159                    }
2160                }
2161              [pool release];
2162            }
2163
2164          // We add members for whom we have their associated login name
2165          for (i = 0; i < [uids count]; i++)
2166            {
2167              pool = [NSAutoreleasePool new];
2168              login = [uids objectAtIndex: i];
2169              user = [SOGoUser userWithLogin: login  roles: nil];
2170              if (user)
2171                {
2172                  contactInfos = [self lookupContactEntryWithUIDorEmail: login inDomain: nil];
2173                  if ([contactInfos objectForKey: @"isGroup"])
2174                    {
2175                      subusers = [self membersForGroupWithUID: login];
2176                      [members addObjectsFromArray: subusers];
2177                    }
2178                  else
2179                    {
2180                      [members addObject: user];
2181                    }
2182                }
2183              [pool release];
2184            }
2185
2186          // We are done fetching members, let's cache the members of the group
2187          // (ie., their UIDs) in memcached to speed up -groupWithUIDHasMemberWithUID.
2188          logins = [members resultsOfSelector: @selector (loginInDomain)];
2189          [[SOGoCache sharedCache] setValue: [logins componentsJoinedByString: @","]
2190                                     forKey: [NSString stringWithFormat: @"%@+%@", uid, _domain]];
2191        }
2192      else
2193        {
2194          // We deal with a dynamic group, let's search all users for whom
2195          // memberOf is equal to our group's DN.
2196          // We also need to look for labelelURI?
2197        }
2198    }
2199
2200  return members;
2201}
2202
2203//
2204//
2205//
2206- (BOOL) groupWithUIDHasMemberWithUID: (NSString *) uid
2207                            memberUid: (NSString *) memberUid
2208{
2209
2210  BOOL rc;
2211  NSString *key, *value;;
2212  NSArray *a;
2213
2214  rc = NO;
2215
2216  if ([uid hasPrefix: @"@"])
2217    uid = [uid substringFromIndex: 1];
2218
2219  key = [NSString stringWithFormat: @"%@+%@", uid, _domain];
2220  value = [[SOGoCache sharedCache] valueForKey: key];
2221
2222  // If the value isn't in memcached, that probably means -members was never called.
2223  // We call it only once here.
2224  if (!value)
2225    {
2226      [self membersForGroupWithUID: uid];
2227      value = [[SOGoCache sharedCache] valueForKey: key];
2228    }
2229
2230  a = [value componentsSeparatedByString: @","];
2231  rc = [a containsObject: memberUid];
2232
2233  return rc;
2234}
2235
2236@end
2237