sogo/SoObjects/SOGo/LDAPSource.m
Francis Lachapelle eb90760b39 Use address books search fields in Contacts module
Searches can now be scoped to one or multiple fields. Those fields are
now dynamic and can be defined using SearchFieldNames in external
contacts sources (SQL and LDAP).
2017-11-21 15:56:16 -05:00

1954 lines
60 KiB
Objective-C

/* LDAPSource.m - this file is part of SOGo
*
* Copyright (C) 2007-2015 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <NGExtensions/NSObject+Logs.h>
#import <EOControl/EOControl.h>
#import <NGLdap/NGLdapConnection.h>
#import <NGLdap/NGLdapAttribute.h>
#import <NGLdap/NGLdapEntry.h>
#import <NGLdap/NGLdapModification.h>
#import <NGLdap/NSString+DN.h>
#import "LDAPSourceSchema.h"
#import "NSArray+Utilities.h"
#import "NSString+Utilities.h"
#import "NSString+Crypto.h"
#import "SOGoCache.h"
#import "SOGoSystemDefaults.h"
#import "LDAPSource.h"
static Class NSStringK;
#define SafeLDAPCriteria(x) [[[x stringByReplacingString: @"\\" withString: @"\\\\"] \
stringByReplacingString: @"'" withString: @"\\'"] \
stringByReplacingString: @"%" withString: @"%%"]
@implementation LDAPSource
+ (void) initialize
{
NSStringK = [NSString class];
}
//
//
//
+ (id) sourceFromUDSource: (NSDictionary *) udSource
inDomain: (NSString *) sourceDomain
{
id newSource;
newSource = [[self alloc] initFromUDSource: udSource
inDomain: sourceDomain];
[newSource autorelease];
return newSource;
}
//
//
//
- (id) init
{
if ((self = [super init]))
{
sourceID = nil;
displayName = nil;
bindDN = nil;
password = nil;
sourceBindDN = nil;
sourceBindPassword = nil;
hostname = nil;
port = 389;
encryption = nil;
domain = nil;
baseDN = nil;
schema = nil;
IDField = @"cn"; /* the first part of a user DN */
CNField = @"cn";
UIDField = @"uid";
mailFields = [NSArray arrayWithObject: @"mail"];
[mailFields retain];
contactMapping = nil;
// "mail" expands to all entries of MailFieldNames
// "name" expands to sn, displayname and cn
searchFields = [NSArray arrayWithObjects: @"name", @"mail", @"telephonenumber", nil];
[searchFields retain];
groupObjectClasses = [NSArray arrayWithObjects: @"group", @"groupofnames", @"groupofuniquenames", @"posixgroup", nil];
[groupObjectClasses retain];
IMAPHostField = nil;
IMAPLoginField = nil;
SieveHostField = nil;
bindFields = nil;
_scope = @"sub";
_filter = nil;
_userPasswordAlgorithm = nil;
listRequiresDot = YES;
passwordPolicy = NO;
updateSambaNTLMPasswords = NO;
kindField = nil;
multipleBookingsField = nil;
MSExchangeHostname = nil;
modifiers = nil;
}
return self;
}
//
//
//
- (void) dealloc
{
[schema release];
[bindDN release];
[password release];
[sourceBindDN release];
[sourceBindPassword release];
[hostname release];
[encryption release];
[baseDN release];
[IDField release];
[CNField release];
[UIDField release];
[contactMapping release];
[mailFields release];
[searchFields release];
[groupObjectClasses release];
[IMAPHostField release];
[IMAPLoginField release];
[SieveHostField release];
[bindFields release];
[_filter release];
[_userPasswordAlgorithm release];
[sourceID release];
[modulesConstraints release];
[_scope release];
[domain release];
[kindField release];
[multipleBookingsField release];
[MSExchangeHostname release];
[modifiers release];
[displayName release];
[super dealloc];
}
//
//
//
- (id) initFromUDSource: (NSDictionary *) udSource
inDomain: (NSString *) sourceDomain
{
SOGoDomainDefaults *dd;
NSNumber *udQueryLimit, *udQueryTimeout, *dotValue;
if ((self = [self init]))
{
[self setSourceID: [udSource objectForKey: @"id"]];
[self setDisplayName: [udSource objectForKey: @"displayName"]];
[self setBindDN: [udSource objectForKey: @"bindDN"]
password: [udSource objectForKey: @"bindPassword"]
hostname: [udSource objectForKey: @"hostname"]
port: [udSource objectForKey: @"port"]
encryption: [udSource objectForKey: @"encryption"]
bindAsCurrentUser: [udSource objectForKey: @"bindAsCurrentUser"]];
[self setBaseDN: [udSource objectForKey: @"baseDN"]
IDField: [udSource objectForKey: @"IDFieldName"]
CNField: [udSource objectForKey: @"CNFieldName"]
UIDField: [udSource objectForKey: @"UIDFieldName"]
mailFields: [udSource objectForKey: @"MailFieldNames"]
searchFields: [udSource objectForKey: @"SearchFieldNames"]
groupObjectClasses: [udSource objectForKey: @"GroupObjectClasses"]
IMAPHostField: [udSource objectForKey: @"IMAPHostFieldName"]
IMAPLoginField: [udSource objectForKey: @"IMAPLoginFieldName"]
SieveHostField: [udSource objectForKey: @"SieveHostFieldName"]
bindFields: [udSource objectForKey: @"bindFields"]
kindField: [udSource objectForKey: @"KindFieldName"]
andMultipleBookingsField: [udSource objectForKey: @"MultipleBookingsFieldName"]];
dotValue = [udSource objectForKey: @"listRequiresDot"];
if (dotValue)
[self setListRequiresDot: [dotValue boolValue]];
[self setContactMapping: [udSource objectForKey: @"mapping"]
andObjectClasses: [udSource objectForKey: @"objectClasses"]];
[self setModifiers: [udSource objectForKey: @"modifiers"]];
ASSIGN (abOU, [udSource objectForKey: @"abOU"]);
if ([sourceDomain length])
{
dd = [SOGoDomainDefaults defaultsForDomain: sourceDomain];
ASSIGN (domain, sourceDomain);
}
else
dd = [SOGoSystemDefaults sharedSystemDefaults];
contactInfoAttribute
= [udSource objectForKey: @"SOGoLDAPContactInfoAttribute"];
if (!contactInfoAttribute)
contactInfoAttribute = [dd ldapContactInfoAttribute];
[contactInfoAttribute retain];
udQueryLimit = [udSource objectForKey: @"SOGoLDAPQueryLimit"];
if (udQueryLimit)
queryLimit = [udQueryLimit intValue];
else
queryLimit = [dd ldapQueryLimit];
udQueryTimeout = [udSource objectForKey: @"SOGoLDAPQueryTimeout"];
if (udQueryTimeout)
queryTimeout = [udQueryTimeout intValue];
else
queryTimeout = [dd ldapQueryTimeout];
ASSIGN(modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
ASSIGN(_filter, [udSource objectForKey: @"filter"]);
ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]);
ASSIGN(_scope, ([udSource objectForKey: @"scope"]
? [udSource objectForKey: @"scope"]
: (id)@"sub"));
if (!_userPasswordAlgorithm)
_userPasswordAlgorithm = @"none";
if ([udSource objectForKey: @"passwordPolicy"])
passwordPolicy = [[udSource objectForKey: @"passwordPolicy"] boolValue];
if ([udSource objectForKey: @"updateSambaNTLMPasswords"])
updateSambaNTLMPasswords = [[udSource objectForKey: @"updateSambaNTLMPasswords"] boolValue];
ASSIGN(MSExchangeHostname, [udSource objectForKey: @"MSExchangeHostname"]);
}
return self;
}
- (void) setBindDN: (NSString *) theDN
{
//NSLog(@"Setting bind DN to %@", theDN);
ASSIGN(bindDN, theDN);
}
- (NSString *) bindDN
{
return bindDN;
}
- (void) setBindPassword: (NSString *) thePassword
{
ASSIGN (password, thePassword);
}
- (NSString *) bindPassword
{
return password;
}
- (BOOL) bindAsCurrentUser
{
return _bindAsCurrentUser;
}
- (void) setBindDN: (NSString *) newBindDN
password: (NSString *) newBindPassword
hostname: (NSString *) newBindHostname
port: (NSString *) newBindPort
encryption: (NSString *) newEncryption
bindAsCurrentUser: (NSString *) bindAsCurrentUser
{
ASSIGN(bindDN, newBindDN);
ASSIGN(password, newBindPassword);
ASSIGN(sourceBindDN, newBindDN);
ASSIGN(sourceBindPassword, newBindPassword);
ASSIGN(encryption, [newEncryption uppercaseString]);
if ([encryption isEqualToString: @"SSL"])
port = 636;
ASSIGN(hostname, newBindHostname);
if (newBindPort)
port = [newBindPort intValue];
_bindAsCurrentUser = [bindAsCurrentUser boolValue];
}
//
//
//
- (void) setBaseDN: (NSString *) newBaseDN
IDField: (NSString *) newIDField
CNField: (NSString *) newCNField
UIDField: (NSString *) newUIDField
mailFields: (NSArray *) newMailFields
searchFields: (NSArray *) newSearchFields
groupObjectClasses: (NSArray *) newGroupObjectClasses
IMAPHostField: (NSString *) newIMAPHostField
IMAPLoginField: (NSString *) newIMAPLoginField
SieveHostField: (NSString *) newSieveHostField
bindFields: (id) newBindFields
kindField: (NSString *) newKindField
andMultipleBookingsField: (NSString *) newMultipleBookingsField
{
ASSIGN(baseDN, [newBaseDN lowercaseString]);
if (newIDField)
ASSIGN(IDField, [newIDField lowercaseString]);
if (newCNField)
ASSIGN(CNField, [newCNField lowercaseString]);
if (newUIDField)
ASSIGN(UIDField, [newUIDField lowercaseString]);
if (newIMAPHostField)
ASSIGN(IMAPHostField, [newIMAPHostField lowercaseString]);
if (newIMAPLoginField)
ASSIGN(IMAPLoginField, [newIMAPLoginField lowercaseString]);
if (newSieveHostField)
ASSIGN(SieveHostField, [newSieveHostField lowercaseString]);
if (newMailFields)
ASSIGN(mailFields, newMailFields);
if (newSearchFields)
ASSIGN(searchFields, newSearchFields);
if (newGroupObjectClasses)
ASSIGN(groupObjectClasses, newGroupObjectClasses);
if (newBindFields)
{
// Before SOGo v1.2.0, bindFields was a comma-separated list
// of values. So it could be configured as:
//
// bindFields = foo;
// bindFields = "foo, bar, baz";
//
// SOGo v1.2.0 and upwards redefined that parameter as an array
// so we would have instead:
//
// bindFields = (foo);
// bindFields = (foo, bar, baz);
//
// We check for the old format and we support it.
if ([newBindFields isKindOfClass: [NSArray class]])
ASSIGN(bindFields, newBindFields);
else
{
[self logWithFormat: @"WARNING: using old bindFields format - please update it"];
ASSIGN(bindFields, [newBindFields componentsSeparatedByString: @","]);
}
}
if (newKindField)
ASSIGN(kindField, [newKindField lowercaseString]);
if (newMultipleBookingsField)
ASSIGN(multipleBookingsField, [newMultipleBookingsField lowercaseString]);
}
- (void) setListRequiresDot: (BOOL) aBool
{
listRequiresDot = aBool;
}
- (BOOL) listRequiresDot
{
return listRequiresDot;
}
- (NSArray *) searchFields
{
return searchFields;
}
- (void) setContactMapping: (NSDictionary *) newMapping
andObjectClasses: (NSArray *) newObjectClasses
{
ASSIGN (contactMapping, newMapping);
ASSIGN (contactObjectClasses, newObjectClasses);
}
//
//
//
- (BOOL) _setupEncryption: (NGLdapConnection *) encryptedConn
{
BOOL rc;
if ([encryption isEqualToString: @"SSL"])
rc = [encryptedConn useSSL];
else if ([encryption isEqualToString: @"STARTTLS"])
rc = [encryptedConn startTLS];
else
{
[self errorWithFormat:
@"encryption scheme '%@' not supported:"
@" use 'SSL' or 'STARTTLS'", encryption];
rc = NO;
}
return rc;
}
//
//
//
- (NGLdapConnection *) _ldapConnection
{
NGLdapConnection *ldapConnection;
NS_DURING
{
//NSLog(@"Creating NGLdapConnection instance for bindDN '%@'", bindDN);
ldapConnection = [[NGLdapConnection alloc] initWithHostName: hostname
port: port];
[ldapConnection autorelease];
if (![encryption length] || [self _setupEncryption: ldapConnection])
{
[ldapConnection bindWithMethod: @"simple"
binddn: bindDN
credentials: password];
if (queryLimit > 0)
[ldapConnection setQuerySizeLimit: queryLimit];
if (queryTimeout > 0)
[ldapConnection setQueryTimeLimit: queryTimeout];
if (!schema)
{
schema = [LDAPSourceSchema new];
[schema readSchemaFromConnection: ldapConnection];
}
}
else
ldapConnection = nil;
}
NS_HANDLER
{
[self errorWithFormat: @"Could not bind to the LDAP server %@ (%d) "
@"using the bind DN: %@", hostname, port, bindDN];
[self errorWithFormat: @"%@", localException];
ldapConnection = nil;
}
NS_ENDHANDLER;
return ldapConnection;
}
- (NSString *) domain
{
return domain;
}
/* user management */
- (EOQualifier *) _qualifierForBindFilter: (NSString *) uid
{
NSMutableString *qs;
NSString *escapedUid;
NSEnumerator *fields;
NSString *currentField;
qs = [NSMutableString string];
escapedUid = SafeLDAPCriteria(uid);
fields = [bindFields objectEnumerator];
while ((currentField = [fields nextObject]))
[qs appendFormat: @" OR (%@='%@')", currentField, escapedUid];
if (_filter && [_filter length])
[qs appendFormat: @" AND %@", _filter];
[qs deleteCharactersInRange: NSMakeRange(0, 4)];
return [EOQualifier qualifierWithQualifierFormat: qs];
}
- (NSString *) _fetchUserDNForLogin: (NSString *) loginToCheck
{
NSEnumerator *entries;
EOQualifier *qualifier;
NSArray *attributes;
NGLdapConnection *ldapConnection;
NSString *userDN;
ldapConnection = [self _ldapConnection];
qualifier = [self _qualifierForBindFilter: loginToCheck];
attributes = [NSArray arrayWithObject: @"dn"];
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
entries = [ldapConnection baseSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
entries = [ldapConnection flatSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
else
entries = [ldapConnection deepSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
userDN = [[entries nextObject] dn];
return userDN;
}
//
//
//
- (BOOL) checkLogin: (NSString *) _login
password: (NSString *) _pwd
perr: (SOGoPasswordPolicyError *) _perr
expire: (int *) _expire
grace: (int *) _grace
{
NGLdapConnection *bindConnection;
NSString *userDN;
BOOL didBind;
didBind = NO;
NS_DURING
if ([_login length] > 0 && [_pwd length] > 0)
{
bindConnection = [[NGLdapConnection alloc] initWithHostName: hostname
port: port];
if (![encryption length] || [self _setupEncryption: bindConnection])
{
if (queryTimeout > 0)
[bindConnection setQueryTimeLimit: queryTimeout];
userDN = [[SOGoCache sharedCache] distinguishedNameForLogin: _login];
if (!userDN)
{
if (bindFields)
{
// We MUST always use the source's bindDN/password in
// order to lookup the user's DN. This is important since
// if we use bindAsCurrentUser, we could stay bound and
// lookup the user's DN (for an other user that is trying
// to log in) but not be able to do so due to ACLs in LDAP.
[self setBindDN: sourceBindDN];
[self setBindPassword: sourceBindPassword];
userDN = [self _fetchUserDNForLogin: _login];
}
else
userDN = [NSString stringWithFormat: @"%@=%@,%@",
IDField, [_login escapedForLDAPDN], baseDN];
}
if (userDN)
{
if (!passwordPolicy)
didBind = [bindConnection bindWithMethod: @"simple"
binddn: userDN
credentials: _pwd];
else
didBind = [bindConnection bindWithMethod: @"simple"
binddn: userDN
credentials: _pwd
perr: (void *)_perr
expire: _expire
grace: _grace];
if (didBind)
// We cache the _login <-> userDN entry to speed up things
[[SOGoCache sharedCache] setDistinguishedName: userDN
forLogin: _login];
}
}
}
NS_HANDLER
{
[self logWithFormat: @"%@", localException];
}
NS_ENDHANDLER;
[bindConnection release];
return didBind;
}
/**
* Encrypts a string using this source password algorithm.
* @param plainPassword the unencrypted password.
* @return a new encrypted string.
* @see _isPassword:equalTo:
*/
- (NSString *) _encryptPassword: (NSString *) plainPassword
{
NSString *pass;
pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
if (pass == nil)
{
[self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm];
return nil;
}
if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5-crypt"] == NSOrderedSame ||
[_userPasswordAlgorithm caseInsensitiveCompare: @"sha256-crypt"] == NSOrderedSame ||
[_userPasswordAlgorithm caseInsensitiveCompare: @"sha512-crypt"] == NSOrderedSame)
{
_userPasswordAlgorithm = @"crypt";
}
return [NSString stringWithFormat: @"{%@}%@", _userPasswordAlgorithm, pass];
}
- (BOOL) _ldapModifyAttribute: (NSString *) theAttribute
withValue: (NSString *) theValue
userDN: (NSString *) theUserDN
password: (NSString *) theUserPassword
connection: (NGLdapConnection *) bindConnection
{
NGLdapModification *mod;
NGLdapAttribute *attr;
NSArray *changes;
BOOL didChange;
attr = [[NGLdapAttribute alloc] initWithAttributeName: theAttribute];
[attr addStringValue: theValue];
mod = [NGLdapModification replaceModification: attr];
changes = [NSArray arrayWithObject: mod];
if ([bindConnection bindWithMethod: @"simple"
binddn: theUserDN
credentials: theUserPassword])
{
didChange = [bindConnection modifyEntryWithDN: theUserDN
changes: changes];
}
else
didChange = NO;
RELEASE(attr);
return didChange;
}
//
//
//
- (BOOL) changePasswordForLogin: (NSString *) login
oldPassword: (NSString *) oldPassword
newPassword: (NSString *) newPassword
perr: (SOGoPasswordPolicyError *) perr
{
NGLdapConnection *bindConnection;
NSString *userDN;
BOOL didChange;
didChange = NO;
NS_DURING
if ([login length] > 0)
{
bindConnection = [[NGLdapConnection alloc] initWithHostName: hostname
port: port];
if (![encryption length] || [self _setupEncryption: bindConnection])
{
if (queryTimeout > 0)
[bindConnection setQueryTimeLimit: queryTimeout];
if (bindFields)
userDN = [self _fetchUserDNForLogin: login];
else
userDN = [NSString stringWithFormat: @"%@=%@,%@",
IDField, [login escapedForLDAPDN], baseDN];
if (userDN)
{
if ([bindConnection isADCompatible])
{
if ([bindConnection bindWithMethod: @"simple"
binddn: userDN
credentials: oldPassword])
{
didChange = [bindConnection changeADPasswordAtDn: userDN
oldPassword: oldPassword
newPassword: newPassword];
}
}
else if (passwordPolicy)
{
didChange = [bindConnection changePasswordAtDn: userDN
oldPassword: oldPassword
newPassword: newPassword
perr: (void *)perr];
}
else
{
// We don't use a password policy - we simply use
// a modify-op to change the password
NSString* encryptedPass;
if ([_userPasswordAlgorithm isEqualToString: @"none"])
{
encryptedPass = newPassword;
}
else
{
encryptedPass = [self _encryptPassword: newPassword];
}
if (encryptedPass != nil)
{
*perr = PolicyNoError;
didChange = [self _ldapModifyAttribute: @"userPassword"
withValue: encryptedPass
userDN: userDN
password: oldPassword
connection: bindConnection];
}
}
// We must check if we must update the Samba NT/LM password hashes
if (didChange && updateSambaNTLMPasswords)
{
[self _ldapModifyAttribute: @"sambaNTPassword"
withValue: [newPassword asNTHash]
userDN: userDN
password: newPassword
connection: bindConnection];
[self _ldapModifyAttribute: @"sambaLMPassword"
withValue: [newPassword asLMHash]
userDN: userDN
password: newPassword
connection: bindConnection];
}
}
}
}
NS_HANDLER
{
if ([[localException name] isEqual: @"LDAPException"] &&
([[[localException userInfo] objectForKey: @"error_code"] intValue] == LDAP_CONSTRAINT_VIOLATION))
{
*perr = PolicyInsufficientPasswordQuality;
}
else
{
[self logWithFormat: @"%@", localException];
}
}
NS_ENDHANDLER ;
[bindConnection release];
return didChange;
}
/**
* Search for contacts matching some string.
* @param filter the string to search for
* @see fetchContactsMatching:
* @return a EOQualifier matching the filter
*/
- (EOQualifier *) _qualifierForFilter: (NSString *) filter
onCriteria: (NSArray *) criteria
{
NSEnumerator *criteriaList;
NSMutableArray *fields;
NSString *fieldFormat, *currentCriteria, *searchFormat, *escapedFilter;
EOQualifier *qualifier;
NSMutableString *qs;
escapedFilter = SafeLDAPCriteria(filter);
qs = [NSMutableString string];
if (([escapedFilter length] == 0 && !listRequiresDot) || [escapedFilter isEqualToString: @"."])
{
[qs appendFormat: @"(%@='*')", CNField];
}
else
{
fieldFormat = [NSString stringWithFormat: @"(%%@='*%@*')", escapedFilter];
if (criteria)
criteriaList = [criteria objectEnumerator];
else
criteriaList = [[self searchFields] objectEnumerator];
fields = [NSMutableArray array];
while (( currentCriteria = [criteriaList nextObject] ))
{
if ([currentCriteria isEqualToString: @"name"])
{
[fields addObject: @"sn"];
[fields addObject: @"displayname"];
[fields addObject: @"cn"];
}
else if ([currentCriteria isEqualToString: @"mail"])
{
// Expand to all mail fields
[fields addObject: currentCriteria];
[fields addObjectsFromArray: mailFields];
}
else if ([[self searchFields] containsObject: currentCriteria])
[fields addObject: currentCriteria];
}
searchFormat = [[[fields uniqueObjects] stringsWithFormat: fieldFormat] componentsJoinedByString: @" OR "];
[qs appendString: searchFormat];
}
if (_filter && [_filter length])
[qs appendFormat: @" AND %@", _filter];
if ([qs length])
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
else
qualifier = nil;
return qualifier;
}
- (EOQualifier *) _qualifierForUIDFilter: (NSString *) uid
{
NSString *mailFormat, *fieldFormat, *escapedUid, *currentField;
NSEnumerator *bindFieldsEnum;
NSMutableString *qs;
escapedUid = SafeLDAPCriteria(uid);
fieldFormat = [NSString stringWithFormat: @"(%%@='%@')", escapedUid];
mailFormat = [[mailFields stringsWithFormat: fieldFormat]
componentsJoinedByString: @" OR "];
qs = [NSMutableString stringWithFormat: @"(%@='%@') OR %@",
UIDField, escapedUid, mailFormat];
if (bindFields)
{
bindFieldsEnum = [bindFields objectEnumerator];
while ((currentField = [bindFieldsEnum nextObject]))
{
if ([currentField caseInsensitiveCompare: UIDField] != NSOrderedSame
&& ![mailFields containsObject: currentField])
[qs appendFormat: @" OR (%@='%@')", [currentField stringByTrimmingSpaces], escapedUid];
}
}
if (_filter && [_filter length])
[qs appendFormat: @" AND %@", _filter];
return [EOQualifier qualifierWithQualifierFormat: qs];
}
/*
- (NSArray *) _constraintsFields
{
NSMutableArray *fields;
NSEnumerator *values;
NSDictionary *currentConstraint;
fields = [NSMutableArray array];
values = [[modulesConstraints allValues] objectEnumerator];
while ((currentConstraint = [values nextObject]))
[fields addObjectsFromArray: [currentConstraint allKeys]];
return fields;
}
*/
/* This is required for SQL sources when DomainFieldName is enabled.
* For LDAP, simply discard the domain and call the original method */
- (NSArray *) allEntryIDsVisibleFromDomain: (NSString *) domain
{
return [self allEntryIDs];
}
- (NSArray *) allEntryIDs
{
NSEnumerator *entries;
NGLdapEntry *currentEntry;
NGLdapConnection *ldapConnection;
EOQualifier *qualifier;
NSMutableString *qs;
NSString *value;
NSArray *attributes;
NSMutableArray *ids;
ids = [NSMutableArray array];
ldapConnection = [self _ldapConnection];
attributes = [NSArray arrayWithObject: IDField];
qs = [NSMutableString stringWithFormat: @"(%@='*')", CNField];
if ([_filter length])
[qs appendFormat: @" AND %@", _filter];
qualifier = [EOQualifier qualifierWithQualifierFormat: qs];
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
entries = [ldapConnection baseSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
entries = [ldapConnection flatSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
else
entries = [ldapConnection deepSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
while ((currentEntry = [entries nextObject]))
{
value = [[currentEntry attributeWithName: IDField]
stringValueAtIndex: 0];
if ([value length] > 0)
[ids addObject: value];
}
return ids;
}
- (void) _fillEmailsOfEntry: (NGLdapEntry *) ldapEntry
intoLDIFRecord: (NSMutableDictionary *) ldifRecord
{
NSEnumerator *emailFields;
NSString *currentFieldName, *ldapValue;
NSMutableArray *emails;
NSArray *allValues;
emails = [[NSMutableArray alloc] init];
emailFields = [mailFields objectEnumerator];
while ((currentFieldName = [emailFields nextObject]))
{
allValues = [[ldapEntry attributeWithName: currentFieldName]
allStringValues];
// Special case handling for Microsoft Active Directory. proxyAddresses
// is generally prefixed with smtp: - if we find this (or any value preceeding
// the semi-colon), we strip it. See https://msdn.microsoft.com/en-us/library/ms679424(v=vs.85).aspx
if ([currentFieldName caseInsensitiveCompare: @"proxyAddresses"] == NSOrderedSame)
{
NSRange r;
int i;
for (i = 0; i < [allValues count]; i++)
{
ldapValue = [allValues objectAtIndex: i];
r = [ldapValue rangeOfString: @":"];
if (r.length)
{
// We only keep "smtp" ones
if ([[ldapValue lowercaseString] hasPrefix: @"smtp"])
[emails addObject: [ldapValue substringFromIndex: r.location+1]];
}
else
[emails addObject: ldapValue];
}
}
else
[emails addObjectsFromArray: allValues];
}
[ldifRecord setObject: emails forKey: @"c_emails"];
[emails release];
if (IMAPHostField)
{
ldapValue = [[ldapEntry attributeWithName: IMAPHostField] stringValueAtIndex: 0];
if ([ldapValue length] > 0)
[ldifRecord setObject: ldapValue forKey: @"c_imaphostname"];
}
if (IMAPLoginField)
{
ldapValue = [[ldapEntry attributeWithName: IMAPLoginField] stringValueAtIndex: 0];
if ([ldapValue length] > 0)
[ldifRecord setObject: ldapValue forKey: @"c_imaplogin"];
}
if (SieveHostField)
{
ldapValue = [[ldapEntry attributeWithName: SieveHostField] stringValueAtIndex: 0];
if ([ldapValue length] > 0)
[ldifRecord setObject: ldapValue forKey: @"c_sievehostname"];
}
}
- (void) _fillConstraints: (NGLdapEntry *) ldapEntry
forModule: (NSString *) module
intoLDIFRecord: (NSMutableDictionary *) ldifRecord
{
NSDictionary *constraints;
NSEnumerator *matches, *ldapValues;
NSString *currentMatch, *currentValue, *ldapValue;
BOOL result;
result = YES;
constraints = [modulesConstraints objectForKey: module];
if (constraints)
{
matches = [[constraints allKeys] objectEnumerator];
while (result == YES && (currentMatch = [matches nextObject]))
{
ldapValues = [[[ldapEntry attributeWithName: currentMatch] allStringValues] objectEnumerator];
currentValue = [constraints objectForKey: currentMatch];
result = NO;
while (result == NO && (ldapValue = [ldapValues nextObject]))
if ([ldapValue caseInsensitiveMatches: currentValue])
result = YES;
}
}
[ldifRecord setObject: [NSNumber numberWithBool: result]
forKey: [NSString stringWithFormat: @"%@Access", module]];
}
/* conversion LDAP -> SOGo inetOrgPerson entry */
- (void) applyContactMappingToResult: (NSMutableDictionary *) ldifRecord
{
NSArray *sourceFields;
NSArray *keys;
NSString *key, *field, *value;
NSUInteger count, max, fieldCount, fieldMax;
BOOL filled;
keys = [contactMapping allKeys];
max = [keys count];
for (count = 0; count < max; count++)
{
key = [keys objectAtIndex: count];
sourceFields = [contactMapping objectForKey: key];
if ([sourceFields isKindOfClass: NSStringK])
sourceFields = [NSArray arrayWithObject: sourceFields];
fieldMax = [sourceFields count];
filled = NO;
for (fieldCount = 0;
!filled && fieldCount < fieldMax;
fieldCount++)
{
field = [[sourceFields objectAtIndex: fieldCount] lowercaseString];
value = [ldifRecord objectForKey: field];
if (value)
{
[ldifRecord setObject: value forKey: [key lowercaseString]];
filled = YES;
}
}
}
}
/* conversion SOGo inetOrgPerson entry -> LDAP */
- (void) applyContactMappingToOutput: (NSMutableDictionary *) ldifRecord
{
NSArray *sourceFields;
NSArray *keys;
NSString *key, *lowerKey, *field, *value;
NSUInteger count, max, fieldCount, fieldMax;
if (contactObjectClasses)
[ldifRecord setObject: contactObjectClasses
forKey: @"objectclass"];
keys = [contactMapping allKeys];
max = [keys count];
for (count = 0; count < max; count++)
{
key = [keys objectAtIndex: count];
lowerKey = [key lowercaseString];
value = [ldifRecord objectForKey: lowerKey];
if ([value length] > 0)
{
sourceFields = [contactMapping objectForKey: key];
if ([sourceFields isKindOfClass: NSStringK])
sourceFields = [NSArray arrayWithObject: sourceFields];
fieldMax = [sourceFields count];
for (fieldCount = 0; fieldCount < fieldMax; fieldCount++)
{
field = [[sourceFields objectAtIndex: fieldCount]
lowercaseString];
[ldifRecord setObject: value forKey: field];
}
}
}
}
- (NSDictionary *) _convertLDAPEntryToContact: (NGLdapEntry *) ldapEntry
{
NSMutableDictionary *ldifRecord;
NSString *value;
static NSArray *resourceKinds = nil;
NSMutableArray *classes;
NSEnumerator *gclasses;
NSString *gclass;
id o;
if (!resourceKinds)
resourceKinds = [[NSArray alloc] initWithObjects: @"location", @"thing",
@"group", nil];
ldifRecord = [ldapEntry asDictionary];
[ldifRecord setObject: self forKey: @"source"];
[ldifRecord setObject: [ldapEntry dn] forKey: @"dn"];
// We get our objectClass attribute values. We lowercase
// everything for ease of search after.
o = [ldapEntry objectClasses];
classes = nil;
if (o)
{
int i, c;
classes = [NSMutableArray arrayWithArray: o];
c = [classes count];
for (i = 0; i < c; i++)
[classes replaceObjectAtIndex: i
withObject: [[classes objectAtIndex: i] lowercaseString]];
}
if (classes)
{
// We check if our entry is a resource. We also support
// determining resources based on the KindFieldName attribute
// value - see below.
if ([classes containsObject: @"calendarresource"])
{
[ldifRecord setObject: [NSNumber numberWithInt: 1]
forKey: @"isResource"];
}
else
{
// We check if our entry is a group. If so, we set the
// 'isGroup' custom attribute.
gclasses = [groupObjectClasses objectEnumerator];
while ((gclass = [gclasses nextObject]))
if ([classes containsObject: [gclass lowercaseString]])
{
[ldifRecord setObject: [NSNumber numberWithInt: 1]
forKey: @"isGroup"];
break;
}
}
}
// We check if that entry corresponds to a resource. For this,
// kindField must be defined and it must hold one of those values
//
// location
// thing
// group
//
if ([kindField length] > 0)
{
value = [ldifRecord objectForKey: [kindField lowercaseString]];
if ([value isKindOfClass: NSStringK]
&& [resourceKinds containsObject: value])
[ldifRecord setObject: [NSNumber numberWithInt: 1]
forKey: @"isResource"];
}
// We check for the number of simultanous bookings that is allowed.
// A value of 0 means that there's no limit.
if ([multipleBookingsField length] > 0)
{
value = [ldifRecord objectForKey: [multipleBookingsField lowercaseString]];
[ldifRecord setObject: [NSNumber numberWithInt: [value intValue]]
forKey: @"numberOfSimultaneousBookings"];
}
value = [[ldapEntry attributeWithName: IDField] stringValueAtIndex: 0];
if (!value)
value = @"";
[ldifRecord setObject: value forKey: @"c_name"];
value = [[ldapEntry attributeWithName: UIDField] stringValueAtIndex: 0];
if (!value)
value = @"";
// else
// {
// Eventually, we could check at this point if the entry is a group
// and prefix the UID with a "@"
// }
[ldifRecord setObject: value forKey: @"c_uid"];
value = [[ldapEntry attributeWithName: CNField] stringValueAtIndex: 0];
if (!value)
value = @"";
[ldifRecord setObject: value forKey: @"c_cn"];
/* if "displayName" is not set, we use CNField because it must exist */
if (![ldifRecord objectForKey: @"displayname"])
[ldifRecord setObject: value forKey: @"displayname"];
if (contactInfoAttribute)
{
value = [[ldapEntry attributeWithName: contactInfoAttribute]
stringValueAtIndex: 0];
if (!value)
value = @"";
}
else
value = @"";
[ldifRecord setObject: value forKey: @"c_info"];
if (domain)
value = domain;
else
value = @"";
[ldifRecord setObject: value forKey: @"c_domain"];
[self _fillEmailsOfEntry: ldapEntry intoLDIFRecord: ldifRecord];
[self _fillConstraints: ldapEntry forModule: @"Calendar"
intoLDIFRecord: (NSMutableDictionary *) ldifRecord];
[self _fillConstraints: ldapEntry forModule: @"Mail"
intoLDIFRecord: (NSMutableDictionary *) ldifRecord];
[self _fillConstraints: ldapEntry forModule: @"ActiveSync"
intoLDIFRecord: (NSMutableDictionary *) ldifRecord];
if (contactMapping)
[self applyContactMappingToResult: ldifRecord];
return ldifRecord;
}
- (NSArray *) fetchContactsMatching: (NSString *) match
withCriteria: (NSArray *) criteria
inDomain: (NSString *) domain
{
NGLdapConnection *ldapConnection;
NGLdapEntry *currentEntry;
NSEnumerator *entries;
NSMutableArray *contacts;
EOQualifier *qualifier;
NSArray *attributes;
contacts = [NSMutableArray array];
if ([match length] > 0 || !listRequiresDot)
{
ldapConnection = [self _ldapConnection];
qualifier = [self _qualifierForFilter: match onCriteria: criteria];
attributes = [NSArray arrayWithObject: @"*"];
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
entries = [ldapConnection baseSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
entries = [ldapConnection flatSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
else /* we do it like before */
entries = [ldapConnection deepSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
while ((currentEntry = [entries nextObject]))
[contacts addObject:
[self _convertLDAPEntryToContact: currentEntry]];
}
return contacts;
}
- (NGLdapEntry *) _lookupLDAPEntry: (EOQualifier *) qualifier
{
NGLdapConnection *ldapConnection;
NSEnumerator *entries;
NSArray *attributes;
ldapConnection = [self _ldapConnection];
attributes = [NSArray arrayWithObject: @"*"];
if ([_scope caseInsensitiveCompare: @"BASE"] == NSOrderedSame)
entries = [ldapConnection baseSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
else if ([_scope caseInsensitiveCompare: @"ONE"] == NSOrderedSame)
entries = [ldapConnection flatSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
else
entries = [ldapConnection deepSearchAtBaseDN: baseDN
qualifier: qualifier
attributes: attributes];
return [entries nextObject];
}
- (NSDictionary *) lookupContactEntry: (NSString *) theID
inDomain: (NSString *) domain
{
NGLdapEntry *ldapEntry;
EOQualifier *qualifier;
NSString *s;
NSDictionary *ldifRecord;
ldifRecord = nil;
if ([theID length] > 0)
{
s = [NSString stringWithFormat: @"(%@='%@')",
IDField, SafeLDAPCriteria(theID)];
qualifier = [EOQualifier qualifierWithQualifierFormat: s];
ldapEntry = [self _lookupLDAPEntry: qualifier];
if (ldapEntry)
ldifRecord = [self _convertLDAPEntryToContact: ldapEntry];
}
return ldifRecord;
}
- (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) uid
inDomain: (NSString *) domain
{
NGLdapEntry *ldapEntry;
EOQualifier *qualifier;
NSDictionary *ldifRecord;
ldifRecord = nil;
if ([uid length] > 0)
{
qualifier = [self _qualifierForUIDFilter: uid];
ldapEntry = [self _lookupLDAPEntry: qualifier];
if (ldapEntry)
ldifRecord = [self _convertLDAPEntryToContact: ldapEntry];
}
return ldifRecord;
}
- (NSString *) lookupLoginByDN: (NSString *) theDN
{
NGLdapConnection *ldapConnection;
NGLdapEntry *entry;
EOQualifier *qualifier;
NSString *login;
login = nil;
qualifier = nil;
ldapConnection = [self _ldapConnection];
if (_filter)
qualifier = [EOQualifier qualifierWithQualifierFormat: _filter];
entry = [ldapConnection entryAtDN: theDN
qualifier: qualifier
attributes: [NSArray arrayWithObject: UIDField]];
if (entry)
login = [[entry attributeWithName: UIDField] stringValueAtIndex: 0];
return login;
}
- (NSString *) lookupDNByLogin: (NSString *) theLogin
{
return [[SOGoCache sharedCache] distinguishedNameForLogin: theLogin];
}
- (NGLdapEntry *) _lookupGroupEntryByAttributes: (NSArray *) theAttributes
andValue: (NSString *) theValue
{
EOQualifier *qualifier;
NGLdapEntry *ldapEntry;
NSString *s;
if ([theValue length] > 0 && [theAttributes count] > 0)
{
if ([theAttributes count] == 1)
{
s = [NSString stringWithFormat: @"(%@='%@')",
[theAttributes lastObject], SafeLDAPCriteria(theValue)];
}
else
{
NSString *fieldFormat;
fieldFormat = [NSString stringWithFormat: @"(%%@='%@')", SafeLDAPCriteria(theValue)];
s = [[theAttributes stringsWithFormat: fieldFormat]
componentsJoinedByString: @" OR "];
}
qualifier = [EOQualifier qualifierWithQualifierFormat: s];
ldapEntry = [self _lookupLDAPEntry: qualifier];
}
else
ldapEntry = nil;
return ldapEntry;
}
- (NGLdapEntry *) lookupGroupEntryByUID: (NSString *) theUID
inDomain: (NSString *) domain
{
return [self _lookupGroupEntryByAttributes: [NSArray arrayWithObject: UIDField]
andValue: theUID];
}
- (NGLdapEntry *) lookupGroupEntryByEmail: (NSString *) theEmail
inDomain: (NSString *) domain
{
return [self _lookupGroupEntryByAttributes: mailFields
andValue: theEmail];
}
- (void) setSourceID: (NSString *) newSourceID
{
ASSIGN (sourceID, newSourceID);
}
- (NSString *) sourceID
{
return sourceID;
}
- (void) setDisplayName: (NSString *) newDisplayName
{
ASSIGN (displayName, newDisplayName);
}
- (NSString *) displayName
{
return displayName;
}
- (NSString *) baseDN
{
return baseDN;
}
- (NSString *) MSExchangeHostname
{
return MSExchangeHostname;
}
- (void) setModifiers: (NSArray *) newModifiers
{
ASSIGN (modifiers, newModifiers);
}
- (NSArray *) modifiers
{
return modifiers;
}
- (NSArray *) groupObjectClasses
{
return groupObjectClasses;
}
static NSArray *
_convertRecordToLDAPAttributes (LDAPSourceSchema *schema, NSDictionary *ldifRecord)
{
/* convert resulting record to NGLdapEntry:
- strip non-existing object classes
- ignore fields with empty values
- ignore extra fields
- use correct case for LDAP attribute matching classes */
NSMutableArray *validClasses, *validFields, *attributes;
NGLdapAttribute *attribute;
NSArray *classes, *fields, *values;
NSString *objectClass, *field, *lowerField, *value;
NSUInteger count, max, valueCount, valueMax;
classes = [ldifRecord objectForKey: @"objectclass"];
if ([classes isKindOfClass: NSStringK])
classes = [NSArray arrayWithObject: classes];
max = [classes count];
validClasses = [NSMutableArray array];
validFields = [NSMutableArray array];
for (count = 0; count < max; count++)
{
objectClass = [classes objectAtIndex: count];
fields = [schema fieldsForClass: objectClass];
if ([fields count] > 0)
{
[validClasses addObject: objectClass];
[validFields addObjectsFromArray: fields];
}
}
[validFields removeDoubles];
attributes = [NSMutableArray new];
max = [validFields count];
for (count = 0; count < max; count++)
{
attribute = nil;
field = [validFields objectAtIndex: count];
lowerField = [field lowercaseString];
if (![lowerField isEqualToString: @"dn"])
{
if ([lowerField isEqualToString: @"objectclass"])
values = validClasses;
else
{
values = [ldifRecord objectForKey: lowerField];
if ([values isKindOfClass: NSStringK])
values = [NSArray arrayWithObject: values];
}
valueMax = [values count];
for (valueCount = 0; valueCount < valueMax; valueCount++)
{
value = [values objectAtIndex: valueCount];
if ([value length] > 0)
{
if (!attribute)
{
attribute = [[NGLdapAttribute alloc]
initWithAttributeName: field];
[attributes addObject: attribute];
[attribute release];
}
[attribute addStringValue: value];
}
}
}
}
return attributes;
}
- (NSException *) addContactEntry: (NSDictionary *) roLdifRecord
withID: (NSString *) aId
{
NSException *result;
NGLdapEntry *newEntry;
NSMutableDictionary *ldifRecord;
NSArray *attributes;
NSString *dn, *cnValue;
NGLdapConnection *ldapConnection;
if ([aId length] > 0)
{
ldapConnection = [self _ldapConnection];
ldifRecord = [roLdifRecord mutableCopy];
[ldifRecord autorelease];
[ldifRecord setObject: aId forKey: UIDField];
/* if CN is not set, we use aId because it must exist */
if (![ldifRecord objectForKey: CNField])
{
cnValue = [ldifRecord objectForKey: @"displayname"];
if ([cnValue length] == 0)
cnValue = aId;
[ldifRecord setObject: aId forKey: @"cn"];
}
[self applyContactMappingToOutput: ldifRecord];
/* since the id might have changed due to the mapping above, we
reload the record ID */
aId = [ldifRecord objectForKey: UIDField];
dn = [NSString stringWithFormat: @"%@=%@,%@", IDField,
[aId escapedForLDAPDN], baseDN];
attributes = _convertRecordToLDAPAttributes (schema, ldifRecord);
newEntry = [[NGLdapEntry alloc] initWithDN: dn
attributes: attributes];
[newEntry autorelease];
[attributes release];
NS_DURING
{
[ldapConnection addEntry: newEntry];
result = nil;
}
NS_HANDLER
{
result = localException;
[result retain];
}
NS_ENDHANDLER;
[result autorelease];
}
else
[self errorWithFormat: @"no value for id field '%@'", IDField];
return result;
}
static NSArray *
_makeLDAPChanges (NGLdapConnection *ldapConnection,
NSString *dn, NSArray *attributes)
{
NSMutableArray *changes, *attributeNames, *origAttributeNames;
NGLdapEntry *origEntry;
// NSArray *values;
NGLdapAttribute *attribute, *origAttribute;
NSString *name;
NSDictionary *origAttributes;
NSUInteger count, max/* , valueCount, valueMax */;
// BOOL allStrings;
/* additions and modifications */
origEntry = [ldapConnection entryAtDN: dn
attributes: [NSArray arrayWithObject: @"*"]];
origAttributes = [origEntry attributes];
max = [attributes count];
changes = [NSMutableArray arrayWithCapacity: max];
attributeNames = [NSMutableArray arrayWithCapacity: max];
for (count = 0; count < max; count++)
{
attribute = [attributes objectAtIndex: count];
name = [attribute attributeName];
[attributeNames addObject: name];
origAttribute = [origAttributes objectForKey: name];
if (origAttribute)
{
if (![origAttribute isEqual: attribute])
[changes
addObject: [NGLdapModification replaceModification: attribute]];
}
else
[changes addObject: [NGLdapModification addModification: attribute]];
}
/* deletions */
origAttributeNames = [[origAttributes allKeys] mutableCopy];
[origAttributeNames autorelease];
[origAttributeNames removeObjectsInArray: attributeNames];
max = [origAttributeNames count];
for (count = 0; count < max; count++)
{
name = [origAttributeNames objectAtIndex: count];
origAttribute = [origAttributes objectForKey: name];
/* the attribute must only have string values, otherwise it will anyway
be missing from the new record */
// allStrings = YES;
// values = [origAttribute allValues];
// valueMax = [values count];
// for (valueCount = 0; allStrings && valueCount < valueMax; valueCount++)
// if (![[values objectAtIndex: valueCount] isKindOfClass: NSStringK])
// allStrings = NO;
// if (allStrings)
[changes
addObject: [NGLdapModification deleteModification: origAttribute]];
}
return changes;
}
- (NSException *) updateContactEntry: (NSDictionary *) roLdifRecord
{
NSException *result;
NSString *dn;
NSMutableDictionary *ldifRecord;
NSArray *attributes, *changes;
NGLdapConnection *ldapConnection;
dn = [roLdifRecord objectForKey: @"dn"];
if ([dn length] > 0)
{
ldapConnection = [self _ldapConnection];
ldifRecord = [roLdifRecord mutableCopy];
[ldifRecord autorelease];
[self applyContactMappingToOutput: ldifRecord];
attributes = _convertRecordToLDAPAttributes (schema, ldifRecord);
changes = _makeLDAPChanges (ldapConnection, dn, attributes);
NS_DURING
{
[ldapConnection modifyEntryWithDN: dn
changes: changes];
result = nil;
}
NS_HANDLER
{
result = localException;
[result retain];
}
NS_ENDHANDLER;
[result autorelease];
}
else
[self errorWithFormat: @"expected dn for modified record"];
return result;
}
- (NSException *) removeContactEntryWithID: (NSString *) aId
{
NSException *result;
NGLdapConnection *ldapConnection;
NSString *dn;
ldapConnection = [self _ldapConnection];
dn = [NSString stringWithFormat: @"%@=%@,%@", IDField,
[aId escapedForLDAPDN], baseDN];
NS_DURING
{
[ldapConnection removeEntryWithDN: dn];
result = nil;
}
NS_HANDLER
{
result = localException;
[result retain];
}
NS_ENDHANDLER;
[result autorelease];
return result;
}
/* user addressbooks */
- (BOOL) hasUserAddressBooks
{
return ([abOU length] > 0);
}
- (NSArray *) addressBookSourcesForUser: (NSString *) user
{
NSMutableArray *sources;
NSString *abBaseDN;
NGLdapConnection *ldapConnection;
NSArray *attributes, *modifier;
NSEnumerator *entries;
NGLdapEntry *entry;
NSMutableDictionary *entryRecord;
NSDictionary *sourceRec;
LDAPSource *ab;
if ([self hasUserAddressBooks])
{
/* list subentries */
sources = [NSMutableArray array];
ldapConnection = [self _ldapConnection];
abBaseDN = [NSString stringWithFormat: @"ou=%@,%@=%@,%@",
[abOU escapedForLDAPDN], IDField,
[user escapedForLDAPDN], baseDN];
/* test ou=addressbooks entry */
attributes = [NSArray arrayWithObject: @"*"];
entries = [ldapConnection baseSearchAtBaseDN: abBaseDN
qualifier: nil
attributes: attributes];
entry = [entries nextObject];
if (entry)
{
attributes = [NSArray arrayWithObjects: @"ou", @"description", nil];
entries = [ldapConnection flatSearchAtBaseDN: abBaseDN
qualifier: nil
attributes: attributes];
modifier = [NSArray arrayWithObject: user];
while ((entry = [entries nextObject]))
{
sourceRec = [entry asDictionary];
ab = [LDAPSource new];
[ab setSourceID: [sourceRec objectForKey: @"ou"]];
[ab setDisplayName: [sourceRec objectForKey: @"description"]];
[ab setBindDN: bindDN
password: password
hostname: hostname
port: [NSString stringWithFormat: @"%d", port]
encryption: encryption
bindAsCurrentUser: [NSString stringWithFormat: @"%d", NO]];
[ab setBaseDN: [entry dn]
IDField: @"cn"
CNField: @"displayName"
UIDField: @"cn"
mailFields: nil
searchFields: nil
groupObjectClasses: nil
IMAPHostField: nil
IMAPLoginField: nil
SieveHostField: nil
bindFields: nil
kindField: nil
andMultipleBookingsField: nil];
[ab setListRequiresDot: NO];
[ab setModifiers: modifier];
[sources addObject: ab];
[ab release];
}
}
else
{
entryRecord = [NSMutableDictionary dictionary];
[entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"];
[entryRecord setObject: @"addressbooks" forKey: @"ou"];
attributes = _convertRecordToLDAPAttributes (schema, entryRecord);
entry = [[NGLdapEntry alloc] initWithDN: abBaseDN
attributes: attributes];
[entry autorelease];
[attributes release];
NS_DURING
{
[ldapConnection addEntry: entry];
}
NS_HANDLER
{
[self errorWithFormat: @"failed to create ou=addressbooks"
@" entry for user"];
}
NS_ENDHANDLER;
}
}
else
sources = nil;
return sources;
}
- (NSException *) addAddressBookSource: (NSString *) newId
withDisplayName: (NSString *) newDisplayName
forUser: (NSString *) user
{
NSException *result;
NSString *abDN;
NGLdapConnection *ldapConnection;
NSArray *attributes;
NGLdapEntry *entry;
NSMutableDictionary *entryRecord;
if ([self hasUserAddressBooks])
{
abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@",
[newId escapedForLDAPDN], [abOU escapedForLDAPDN],
IDField, [user escapedForLDAPDN], baseDN];
entryRecord = [NSMutableDictionary dictionary];
[entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"];
[entryRecord setObject: newId forKey: @"ou"];
if ([newDisplayName length] > 0)
[entryRecord setObject: newDisplayName forKey: @"description"];
ldapConnection = [self _ldapConnection];
attributes = _convertRecordToLDAPAttributes (schema, entryRecord);
entry = [[NGLdapEntry alloc] initWithDN: abDN
attributes: attributes];
[entry autorelease];
[attributes release];
NS_DURING
{
[ldapConnection addEntry: entry];
result = nil;
}
NS_HANDLER
{
[self errorWithFormat: @"failed to create addressbook entry"];
result = localException;
[result retain];
}
NS_ENDHANDLER;
[result autorelease];
}
else
result = [NSException exceptionWithName: @"LDAPSourceIOException"
reason: @"user addressbooks"
@" are not supported"
userInfo: nil];
return result;
}
- (NSException *) renameAddressBookSource: (NSString *) newId
withDisplayName: (NSString *) newDisplayName
forUser: (NSString *) user
{
NSException *result;
NSString *abDN;
NGLdapConnection *ldapConnection;
NSArray *attributes, *changes;
NSMutableDictionary *entryRecord;
if ([self hasUserAddressBooks])
{
abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@",
[newId escapedForLDAPDN], [abOU escapedForLDAPDN],
IDField, [user escapedForLDAPDN], baseDN];
entryRecord = [NSMutableDictionary dictionary];
[entryRecord setObject: @"organizationalUnit" forKey: @"objectclass"];
[entryRecord setObject: newId forKey: @"ou"];
if ([newDisplayName length] > 0)
[entryRecord setObject: newDisplayName forKey: @"description"];
ldapConnection = [self _ldapConnection];
attributes = _convertRecordToLDAPAttributes (schema, entryRecord);
changes = _makeLDAPChanges (ldapConnection, abDN, attributes);
[attributes release];
NS_DURING
{
[ldapConnection modifyEntryWithDN: abDN
changes: changes];
result = nil;
}
NS_HANDLER
{
[self errorWithFormat: @"failed to rename addressbook entry"];
result = localException;
[result retain];
}
NS_ENDHANDLER;
[result autorelease];
}
else
result = [NSException exceptionWithName: @"LDAPSourceIOException"
reason: @"user addressbooks"
@" are not supported"
userInfo: nil];
return result;
}
- (NSException *) removeAddressBookSource: (NSString *) newId
forUser: (NSString *) user
{
NSException *result;
NSString *abDN;
NGLdapConnection *ldapConnection;
NSEnumerator *entries;
NGLdapEntry *entry;
if ([self hasUserAddressBooks])
{
abDN = [NSString stringWithFormat: @"ou=%@,ou=%@,%@=%@,%@",
[newId escapedForLDAPDN], [abOU escapedForLDAPDN],
IDField, [user escapedForLDAPDN], baseDN];
ldapConnection = [self _ldapConnection];
NS_DURING
{
/* we must remove the ab sub=entries prior to the ab entry */
entries = [ldapConnection flatSearchAtBaseDN: abDN
qualifier: nil
attributes: nil];
while ((entry = [entries nextObject]))
[ldapConnection removeEntryWithDN: [entry dn]];
[ldapConnection removeEntryWithDN: abDN];
result = nil;
}
NS_HANDLER
{
[self errorWithFormat: @"failed to remove addressbook entry"];
result = localException;
[result retain];
}
NS_ENDHANDLER;
[result autorelease];
}
else
result = [NSException exceptionWithName: @"LDAPSourceIOException"
reason: @"user addressbooks"
@" are not supported"
userInfo: nil];
return result;
}
@end