435554e99d
Monotone-Revision: 5e35c86af271ef315fde0b5c6b8539dfb710f2d0 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2007-04-17T13:53:13 Monotone-Branch: ca.inverse.sogo
1223 lines
33 KiB
Objective-C
1223 lines
33 KiB
Objective-C
/*
|
|
Copyright (C) 2004-2005 SKYRIX Software AG
|
|
|
|
This file is part of OpenGroupware.org.
|
|
|
|
OGo is free software; you can redistribute it and/or modify it under
|
|
the terms of the GNU Lesser General Public License as published by the
|
|
Free Software Foundation; either version 2, or (at your option) any
|
|
later version.
|
|
|
|
OGo 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 Lesser General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with OGo; see the file COPYING. If not, write to the
|
|
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
|
|
02111-1307, USA.
|
|
*/
|
|
|
|
#include "AgenorUserManager.h"
|
|
#include "AgenorUserDefaults.h"
|
|
#include <NGExtensions/NGExtensions.h>
|
|
#include <NGLdap/NGLdap.h>
|
|
#include <NGCards/NGCards.h>
|
|
#include "SOGoLRUCache.h"
|
|
|
|
#warning we should rely on the LDAP sources instead...
|
|
#define qualifierFormat @"uid = %@"
|
|
|
|
@interface AgenorUserManager (PrivateAPI)
|
|
- (NGLdapConnection *)ldapConnection;
|
|
|
|
- (void)_cacheCN:(NSString *)_cn forUID:(NSString *)_uid;
|
|
- (NSString *)_cachedCNForUID:(NSString *)_uid;
|
|
- (void)_cacheServer:(NSString *)_server forUID:(NSString *)_uid;
|
|
- (NSString *)_cachedServerForUID:(NSString *)_uid;
|
|
- (void)_cacheEmail:(NSString *)_email forUID:(NSString *)_uid;
|
|
- (NSString *)_cachedEmailForUID:(NSString *)_uid;
|
|
- (void)_cacheUID:(NSString *)_uid forEmail:(NSString *)_email;
|
|
- (NSString *)_cachedUIDForEmail:(NSString *)_email;
|
|
|
|
- (BOOL)primaryIsUserAllowedToChangeSOGoInternetAccess:(NSString *)_uid;
|
|
|
|
- (BOOL)primaryIsInternetAutoresponderEnabledForUser:(NSString *)_uid;
|
|
- (BOOL)primaryIsIntranetAutoresponderEnabledForUser:(NSString *)_uid;
|
|
- (NGLdapAttribute *)primaryGetMailAutoresponderAttribute:(NSString *)_uid;
|
|
- (BOOL)isAutoresponderEnabledForAttribute:(NGLdapAttribute *)_attr
|
|
matchingPrefix:(NSString *)_prefix;
|
|
@end
|
|
|
|
// TODO: add a timer to flush LRU caches every some hours
|
|
|
|
@implementation AgenorUserManager
|
|
|
|
static BOOL debugOn = NO;
|
|
static BOOL useLDAP = NO;
|
|
static NSString *ldapHost = nil;
|
|
static NSString *ldapBaseDN = nil;
|
|
static NSNull *sharedNull = nil;
|
|
static NSString *fallbackIMAP4Server = nil;
|
|
static NSString *defaultMailDomain = nil;
|
|
static NSString *shareLDAPClass = @"mineqMelBoite";
|
|
static NSString *shareLoginSeparator = @".-.";
|
|
static NSString *mailEmissionAttrName = @"mineqMelmailEmission";
|
|
static NSString *changeInternetAccessAttrName = @"mineqOgoAccesInternet";
|
|
static NSString *mailAutoresponderAttrName = @"mineqMelReponse"; /* sic! */
|
|
static NSURL *AgenorProfileURL = nil;
|
|
|
|
static NSArray *fromEMailAttrs = nil;
|
|
|
|
static unsigned PoolScanInterval = 5 * 60 /* every five minutes */;
|
|
|
|
+ (void) initialize
|
|
{
|
|
static BOOL didInit = NO;
|
|
NSUserDefaults *ud;
|
|
NSString *tmp;
|
|
|
|
if (didInit) return;
|
|
didInit = YES;
|
|
|
|
ud = [NSUserDefaults standardUserDefaults];
|
|
debugOn = [ud boolForKey:@"SOGoUserManagerDebugEnabled"];
|
|
|
|
useLDAP = [ud boolForKey:@"SOGoUserManagerUsesLDAP"];
|
|
if (useLDAP) {
|
|
ldapHost = [[ud stringForKey:@"LDAPHost"] copy];
|
|
ldapBaseDN = [[ud stringForKey:@"LDAPRootDN"] copy];
|
|
NSLog(@"Note: using LDAP host to manage accounts: %@", ldapHost);
|
|
}
|
|
else
|
|
NSLog(@"Note: LDAP access is disabled.");
|
|
|
|
fallbackIMAP4Server = [[ud stringForKey:@"SOGoFallbackIMAP4Server"] copy];
|
|
if ([fallbackIMAP4Server length] > 0)
|
|
NSLog(@"Note: using fallback IMAP4 server: '%@'", fallbackIMAP4Server);
|
|
else
|
|
{
|
|
if (fallbackIMAP4Server)
|
|
[fallbackIMAP4Server release];
|
|
fallbackIMAP4Server = nil;
|
|
}
|
|
|
|
defaultMailDomain = [[ud stringForKey:@"SOGoDefaultMailDomain"] copy];
|
|
if ([defaultMailDomain length] == 0)
|
|
{
|
|
if (defaultMailDomain)
|
|
[defaultMailDomain release];
|
|
defaultMailDomain = nil;
|
|
NSLog(@"WARNING: no default mail domain (please specify"
|
|
" 'SOGoDefaultMailDomain' in the user default db)");
|
|
}
|
|
|
|
fromEMailAttrs =
|
|
[[NSArray alloc] initWithObjects:mailEmissionAttrName, nil];
|
|
|
|
sharedNull = [[NSNull null] retain];
|
|
|
|
/* profile database URL */
|
|
|
|
if ((tmp = [ud stringForKey:@"AgenorProfileURL"]) == nil)
|
|
NSLog(@"ERROR: no 'AgenorProfileURL' database URL configured!");
|
|
else if ((AgenorProfileURL = [[NSURL alloc] initWithString:tmp]) == nil)
|
|
NSLog(@"ERROR: could not parse AgenorProfileURL: '%@'", tmp);
|
|
else
|
|
NSLog(@"Note: using profile at: %@", [AgenorProfileURL absoluteString]);
|
|
|
|
if ((tmp = [ud stringForKey: @"SOGoDefaultMailDomain"]))
|
|
{
|
|
defaultMailDomain = [tmp copy];
|
|
}
|
|
|
|
PoolScanInterval = [[ud objectForKey:@"AgenorCacheCheckInterval"] intValue];
|
|
if (PoolScanInterval == 0)
|
|
PoolScanInterval = 60 * 60 /* every hour */;
|
|
NSLog(@"AgenorUserManager: flushing caches every %d minutes "
|
|
@"(AgenorCacheCheckInterval)",
|
|
PoolScanInterval / 60);
|
|
}
|
|
|
|
+ (id)sharedUserManager {
|
|
static AgenorUserManager *mgr = nil;
|
|
if (mgr == nil)
|
|
mgr = [[self alloc] init];
|
|
return mgr;
|
|
}
|
|
|
|
- (id)init {
|
|
self = [super init];
|
|
if(self) {
|
|
serverCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
cnCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
uidCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
emailCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
shareStoreCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
shareEMailCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
changeInternetAccessCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
internetAutoresponderFlagCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
intranetAutoresponderFlagCache =
|
|
[[NSMutableDictionary alloc] initWithCapacity:10000];
|
|
|
|
gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
|
|
PoolScanInterval
|
|
target:self selector:@selector(_garbageCollect:)
|
|
userInfo:nil repeats:YES] retain];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
if (gcTimer) [gcTimer invalidate];
|
|
[serverCache release];
|
|
[cnCache release];
|
|
[uidCache release];
|
|
[emailCache release];
|
|
[shareStoreCache release];
|
|
[shareEMailCache release];
|
|
[changeInternetAccessCache release];
|
|
[internetAutoresponderFlagCache release];
|
|
[intranetAutoresponderFlagCache release];
|
|
[gcTimer release];
|
|
[super dealloc];
|
|
}
|
|
|
|
/* cache */
|
|
|
|
- (void)flush {
|
|
[cnCache removeAllObjects];
|
|
[serverCache removeAllObjects];
|
|
[uidCache removeAllObjects];
|
|
[emailCache removeAllObjects];
|
|
[shareStoreCache removeAllObjects];
|
|
[shareEMailCache removeAllObjects];
|
|
|
|
[changeInternetAccessCache removeAllObjects];
|
|
[internetAutoresponderFlagCache removeAllObjects];
|
|
[intranetAutoresponderFlagCache removeAllObjects];
|
|
}
|
|
|
|
- (void)_garbageCollect:(NSTimer *)_timer {
|
|
[self debugWithFormat:@"flushing caches."];
|
|
[self flush];
|
|
}
|
|
|
|
/* LDAP */
|
|
|
|
- (NGLdapConnection *)ldapConnection {
|
|
static NGLdapConnection *ldapConnection = nil;
|
|
if(!ldapConnection) {
|
|
ldapConnection = [[NGLdapConnection alloc] initWithHostName: ldapHost];
|
|
#if 0
|
|
[ldapConnection setUseCache:YES];
|
|
#endif
|
|
}
|
|
return ldapConnection;
|
|
}
|
|
|
|
|
|
/* private cache helpers */
|
|
// TODO: this is really unnecessary, no?
|
|
|
|
- (void)_cacheCN:(NSString *)_cn forUID:(NSString *)_uid {
|
|
if (_cn == nil) return;
|
|
[cnCache setObject:_cn forKey:_uid];
|
|
}
|
|
- (NSString *)_cachedCNForUID:(NSString *)_uid {
|
|
return [cnCache objectForKey:_uid];
|
|
}
|
|
|
|
- (void)_cacheServer:(NSString *)_server forUID:(NSString *)_uid {
|
|
if (_server == nil) return;
|
|
[serverCache setObject:_server forKey:_uid];
|
|
}
|
|
- (NSString *)_cachedServerForUID:(NSString *)_uid {
|
|
return [serverCache objectForKey:_uid];
|
|
}
|
|
|
|
- (void)_cacheEmail:(NSString *)_email forUID:(NSString *)_uid {
|
|
if (_email == nil) return;
|
|
[emailCache setObject:_email forKey:_uid];
|
|
}
|
|
- (NSString *)_cachedEmailForUID:(NSString *)_uid {
|
|
return [emailCache objectForKey:_uid];
|
|
}
|
|
|
|
- (void)_cacheUID:(NSString *)_uid forEmail:(NSString *)_email {
|
|
if (_uid == nil) return;
|
|
[uidCache setObject:_uid forKey:_email];
|
|
}
|
|
- (NSString *)_cachedUIDForEmail:(NSString *)_email {
|
|
return [uidCache objectForKey:_email];
|
|
}
|
|
|
|
|
|
/* uid <-> email mapping */
|
|
|
|
/*
|
|
UPDATE: the email excerpt below has been marked by Maxime as being
|
|
wrong. This algorithm can not be expected to work, thus
|
|
the mapping has been replaced with an LDAP query.
|
|
|
|
--- snip ---
|
|
The uid field is in bijection this the email adress :
|
|
this field can be construct from the email. Email are uniques.
|
|
|
|
So, we can use email adresse from identifier.
|
|
The field is made like this :
|
|
_ if the email is equipement.gouv.fr then the login
|
|
is the part before the @
|
|
for example : fisrtName.lastName
|
|
_ if the email is not equipement.gouv.fr then the login
|
|
is the full email adress where @ is change to . (dot)
|
|
for example : fisrtName.lastName.subDomain.domain.tld
|
|
--- snap ---
|
|
|
|
NOTE: mapping email -> uid is easy, but can also generate uid's not known
|
|
to the system (i.e. for private addressbook entries, obvious).
|
|
The reverse mapping can work _only_ if "firstName.lastname." is
|
|
guaranteed, because the second dot would be mapped to '@'. This
|
|
is probably error prone.
|
|
Only LDAP fetches would guarantee correctness in both cases.
|
|
*/
|
|
|
|
- (NSString *)primaryGetAgenorUIDForEmail:(NSString *)_email {
|
|
static NSArray *uidAttrs = nil;
|
|
NGLdapConnection *conn;
|
|
EOQualifier *q;
|
|
NSEnumerator *resultEnum;
|
|
NGLdapEntry *entry;
|
|
NGLdapAttribute *uidAttr;
|
|
NSString *uid;
|
|
|
|
if (uidAttrs == nil)
|
|
uidAttrs = [[NSArray alloc] initWithObjects:@"uid", nil];
|
|
|
|
q = [EOQualifier qualifierWithQualifierFormat:@"mail = %@", _email];
|
|
|
|
conn = [self ldapConnection];
|
|
resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
|
|
qualifier:q
|
|
attributes:uidAttrs];
|
|
entry = [resultEnum nextObject];
|
|
if (entry == nil) {
|
|
if(debugOn) {
|
|
[self logWithFormat:@"%s Didn't find LDAP entry for email '%@'!",
|
|
__PRETTY_FUNCTION__,
|
|
_email];
|
|
}
|
|
return nil;
|
|
}
|
|
uidAttr = [entry attributeWithName:@"uid"];
|
|
if (!uidAttr)
|
|
return nil; /* can happen, not unlikely */
|
|
uid = [uidAttr stringValueAtIndex:0];
|
|
return uid;
|
|
}
|
|
|
|
- (NSString *) getUIDForEmail: (NSString *)_email
|
|
{
|
|
NSString *uid;
|
|
NSRange r;
|
|
NSString *domain;
|
|
|
|
uid = nil;
|
|
if ([_email length] > 0)
|
|
{
|
|
uid = [self _cachedUIDForEmail:_email];
|
|
if (!uid)
|
|
{
|
|
if (useLDAP)
|
|
uid = [self primaryGetAgenorUIDForEmail:_email];
|
|
|
|
if (!uid)
|
|
{
|
|
r = [_email rangeOfString:@"@"];
|
|
if (r.length == 0)
|
|
return nil;
|
|
|
|
domain = [_email substringFromIndex:NSMaxRange(r)];
|
|
if (![domain isEqualToString:defaultMailDomain])
|
|
uid = _email;
|
|
else
|
|
uid = [_email substringToIndex:r.location];
|
|
}
|
|
if (uid)
|
|
[self _cacheUID:uid forEmail:_email];
|
|
}
|
|
}
|
|
|
|
return uid;
|
|
}
|
|
|
|
#warning big ugly hack. LDAP lookup should be fixed
|
|
- (NSString *) getUIDForICalPerson: (iCalPerson *) _person
|
|
{
|
|
NSString *domainString, *email, *uid;
|
|
|
|
domainString = [NSString stringWithFormat: @"@%@", defaultMailDomain];
|
|
email = [_person rfc822Email];
|
|
if ([email hasSuffix: domainString])
|
|
uid = [_person cn];
|
|
else
|
|
uid = [self getUIDForEmail: email];
|
|
|
|
return uid;
|
|
}
|
|
|
|
/* may insert NSNulls into returned array */
|
|
- (NSArray *)getUIDsForICalPersons:(NSArray *)_persons
|
|
applyStrictMapping:(BOOL)_mapStrictly
|
|
{
|
|
NSMutableArray *ma;
|
|
unsigned i, count;
|
|
|
|
count = [_persons count];
|
|
ma = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
|
|
for (i = 0; i < count; i++) {
|
|
iCalPerson *p;
|
|
id uid;
|
|
|
|
p = [_persons objectAtIndex:i];
|
|
uid = [self getUIDForICalPerson:p];
|
|
if (uid != nil)
|
|
[ma addObject:uid];
|
|
else if (uid == nil && _mapStrictly)
|
|
[ma addObject:sharedNull];
|
|
}
|
|
return ma;
|
|
}
|
|
|
|
- (NSString *)primaryGetEmailForAgenorUID:(NSString *)_uid {
|
|
NGLdapConnection *conn;
|
|
EOQualifier *q;
|
|
NSEnumerator *resultEnum;
|
|
NGLdapEntry *entry;
|
|
NGLdapAttribute *emailAttr;
|
|
NSString *email;
|
|
unsigned count;
|
|
|
|
q = [EOQualifier qualifierWithQualifierFormat: qualifierFormat, _uid];
|
|
|
|
conn = [self ldapConnection];
|
|
resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
|
|
qualifier:q
|
|
attributes:fromEMailAttrs];
|
|
entry = [resultEnum nextObject];
|
|
if (entry == nil) {
|
|
if(debugOn) {
|
|
[self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
|
|
__PRETTY_FUNCTION__,
|
|
_uid];
|
|
}
|
|
return nil;
|
|
}
|
|
emailAttr = [entry attributeWithName:mailEmissionAttrName];
|
|
if (emailAttr == nil)
|
|
return nil; /* shit happens */
|
|
|
|
email = nil;
|
|
count = [emailAttr count];
|
|
#if 0 // TODO: explain why this is commented out!
|
|
if (count > 1) {
|
|
unsigned i;
|
|
|
|
/* in case there are multiple email addresses, select the first
|
|
which doesn't have '@equipement.gouv.fr' in it */
|
|
for (i = 0; i < count; i++) {
|
|
NSString *candidate;
|
|
|
|
candidate = [emailAttr stringValueAtIndex:i];
|
|
if (![candidate hasSuffix:defaultMailDomain]) {
|
|
// TODO: also check for '@'
|
|
email = candidate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
if (email == nil && count > 0)
|
|
email = [emailAttr stringValueAtIndex:0];
|
|
|
|
return email;
|
|
}
|
|
|
|
- (NSString *)getEmailForUID:(NSString *)_uid
|
|
{
|
|
NSString *email;
|
|
NSRange r;
|
|
|
|
email = nil;
|
|
|
|
if ([_uid length] > 0)
|
|
{
|
|
email = [self _cachedEmailForUID: _uid];
|
|
if (!email)
|
|
{
|
|
if (useLDAP)
|
|
email = [self primaryGetEmailForAgenorUID:_uid];
|
|
|
|
if (!email)
|
|
{
|
|
r = [_uid rangeOfString:@"@"];
|
|
email = ((r.length > 0)
|
|
? _uid
|
|
: [[_uid stringByAppendingString:@"@"]
|
|
stringByAppendingString: defaultMailDomain]);
|
|
}
|
|
|
|
if (email)
|
|
[self _cacheEmail: email forUID: _uid];
|
|
}
|
|
}
|
|
|
|
return email;
|
|
}
|
|
|
|
|
|
/* CN */
|
|
|
|
- (NSString *)primaryGetCNForAgenorUID:(NSString *)_uid {
|
|
static NSArray *cnAttrs = nil;
|
|
NGLdapConnection *conn;
|
|
EOQualifier *q;
|
|
NSEnumerator *resultEnum;
|
|
NGLdapEntry *entry;
|
|
NGLdapAttribute *cnAttr;
|
|
NSString *cn;
|
|
|
|
if (cnAttrs == nil)
|
|
cnAttrs = [[NSArray alloc] initWithObjects:@"cn", nil];
|
|
|
|
q = [EOQualifier qualifierWithQualifierFormat: qualifierFormat, _uid];
|
|
|
|
conn = [self ldapConnection];
|
|
resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
|
|
qualifier:q
|
|
attributes:cnAttrs];
|
|
entry = [resultEnum nextObject];
|
|
if (entry == nil) {
|
|
if(debugOn) {
|
|
[self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
|
|
__PRETTY_FUNCTION__,
|
|
_uid];
|
|
}
|
|
return nil;
|
|
}
|
|
cnAttr = [entry attributeWithName:@"cn"];
|
|
if(cnAttr == nil && debugOn) {
|
|
[self logWithFormat:@"%s LDAP entry for uid '%@' has no common name?",
|
|
__PRETTY_FUNCTION__,
|
|
_uid];
|
|
return nil; /* nothing we can do about it */
|
|
}
|
|
cn = [cnAttr stringValueAtIndex:0];
|
|
return cn;
|
|
}
|
|
|
|
- (NSString *)getCNForUID:(NSString *)_uid {
|
|
NSString *cn;
|
|
|
|
if ((cn = [self _cachedCNForUID:_uid]) != nil)
|
|
return cn;
|
|
|
|
if (useLDAP) {
|
|
cn = [self primaryGetCNForAgenorUID:_uid];
|
|
}
|
|
else {
|
|
NSString *s;
|
|
NSRange r;
|
|
|
|
s = _uid;
|
|
if ([s length] < 10)
|
|
return s;
|
|
|
|
// TODO: algorithm might be inappropriate, depends on the actual UID
|
|
r = [s rangeOfString:@"."];
|
|
if (r.length == 0)
|
|
cn = s;
|
|
else
|
|
cn = [s substringToIndex:r.location];
|
|
}
|
|
|
|
[self _cacheCN:cn forUID:_uid];
|
|
return cn;
|
|
}
|
|
|
|
|
|
/* Servers, IMAP */
|
|
|
|
- (NSString *)getIMAPAccountStringForUID:(NSString *)_uid {
|
|
NSString *server;
|
|
|
|
server = [self getServerForUID:_uid];
|
|
if (server == nil)
|
|
return nil;
|
|
return [NSString stringWithFormat:@"%@@%@", _uid, server];
|
|
}
|
|
|
|
- (NSArray *)mailServerDiscoveryAttributes {
|
|
static NSArray *attrs = nil;
|
|
|
|
if (attrs == nil) {
|
|
attrs = [[NSArray alloc] initWithObjects:
|
|
@"uid", /* required for shares */
|
|
@"mineqMelRoutage",
|
|
@"mineqMelServeurPrincipal",
|
|
@"mineqMelPartages",
|
|
mailEmissionAttrName,
|
|
nil];
|
|
}
|
|
return attrs;
|
|
}
|
|
|
|
- (NGLdapEntry *)_fetchEntryForAgenorUID:(NSString *)_uid {
|
|
// TODO: badly named, this fetches the mail server discovery attributes
|
|
/* called by -primaryGetServerForAgenorUID: */
|
|
NGLdapConnection *conn;
|
|
EOQualifier *q;
|
|
NSEnumerator *resultEnum;
|
|
NGLdapEntry *entry;
|
|
|
|
q = [EOQualifier qualifierWithQualifierFormat: qualifierFormat, _uid];
|
|
|
|
conn = [self ldapConnection];
|
|
resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
|
|
qualifier:q
|
|
attributes:[self mailServerDiscoveryAttributes]];
|
|
/* we just expect one entry, thus drop the rest */
|
|
entry = [resultEnum nextObject];
|
|
if (entry == nil) {
|
|
if(debugOn) {
|
|
NSLog(@"%s Didn't find LDAP entry for uid '%@'!",
|
|
__PRETTY_FUNCTION__,
|
|
_uid);
|
|
}
|
|
return nil;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
- (NSArray *)_serverCandidatesForMineqMelRoutage:(NGLdapAttribute *)attr {
|
|
/*
|
|
eg:
|
|
"Baluh.Hommes.Tests-Montee-En-Charge-Ogo%equipement.gouv.fr@\
|
|
amelie-01.ac.melanie2.i2"
|
|
*/
|
|
NSMutableArray *serverCandidates;
|
|
unsigned i, count;
|
|
|
|
count = [attr count];
|
|
serverCandidates = [NSMutableArray arrayWithCapacity:count];
|
|
for (i = 0; i < count; i++) {
|
|
NSRange r;
|
|
NSString *route;
|
|
unsigned length;
|
|
unsigned start;
|
|
NSRange serverNameRange;
|
|
NSString *serverName;
|
|
|
|
route = [attr stringValueAtIndex:i];
|
|
|
|
/* check for melanie suffix and ignore other entries */
|
|
|
|
r = [route rangeOfString:@".melanie2.i2" options:NSBackwardsSearch];
|
|
if (r.length == 0) {
|
|
#if 0
|
|
[self logWithFormat:@"found no melanie in route: '%@'", route];
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
/* check for @ inside the string, searching backwards (ignoring suffix) */
|
|
|
|
// be clever: TODO: in what way is this clever?
|
|
length = [route length];
|
|
r = NSMakeRange(0, length - r.length); /* cut of suffix (.melanie2.i2) */
|
|
r = [route rangeOfString:@"@" options:NSBackwardsSearch range:r];
|
|
if (r.length == 0) {
|
|
#if 0
|
|
[self logWithFormat:@"found no @ in route: '%@'", route];
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
/* check for percent sign */
|
|
|
|
start = NSMaxRange(r); /* start behind the @ */
|
|
|
|
/* this range covers everything after @: 'amelie-01.ac.melanie2.i2' */
|
|
serverNameRange = NSMakeRange(start, length - start);
|
|
|
|
/* and this range covers everything to the @ */
|
|
r = NSMakeRange(0, start - 1);
|
|
r = [route rangeOfString:@"%" options:NSBackwardsSearch range:r];
|
|
if (r.length == 0) {
|
|
#if 0
|
|
[self logWithFormat:@"found no %% in route: '%@' / '%@'",
|
|
route, [route substringWithRange:NSMakeRange(0, length - start)]];
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
serverName = [route substringWithRange:serverNameRange];
|
|
[serverCandidates addObject:serverName];
|
|
}
|
|
return serverCandidates;
|
|
}
|
|
|
|
- (NSString *)serverFromEntry:(NGLdapEntry *)_entry {
|
|
NSString *server;
|
|
NGLdapAttribute *attr;
|
|
|
|
server = nil;
|
|
|
|
attr = [_entry attributeWithName:@"mineqMelRoutage"];
|
|
if (attr != nil) {
|
|
NSArray *serverCandidates;
|
|
|
|
serverCandidates = [self _serverCandidatesForMineqMelRoutage:attr];
|
|
if ([serverCandidates count] > 0)
|
|
server = [serverCandidates objectAtIndex:0];
|
|
|
|
if ([serverCandidates count] > 1) {
|
|
[self logWithFormat:
|
|
@"WARNING: more than one value for 'mineqMelRoutage': %@",
|
|
serverCandidates];
|
|
}
|
|
}
|
|
else {
|
|
[self debugWithFormat:
|
|
@"%s LDAP entry '%@' has no mineqMelRoutage entry?",
|
|
__PRETTY_FUNCTION__, [_entry dn]];
|
|
}
|
|
|
|
/* last resort */
|
|
if (server == nil) {
|
|
attr = [_entry attributeWithName:@"mineqMelServeurPrincipal"];
|
|
if ([attr count] > 0)
|
|
server = [attr stringValueAtIndex:0];
|
|
}
|
|
|
|
return server;
|
|
}
|
|
|
|
- (NSString *)primaryGetServerForAgenorUID:(NSString *)_uid {
|
|
/*
|
|
First of all : for a particular user IMAP and SMTP are served on the same
|
|
host.
|
|
|
|
The name of the machine is determined by applying a regex on every values of
|
|
the mineqMelRoutage LDAP attribute.
|
|
The regex is : .*%.*@(.*\.melanie2\.i2$)
|
|
It extracts the substring that follows '@', ends with 'melanie2', on
|
|
adresses which have a '%' before the '@'
|
|
|
|
Example: helge.hesse%opengroupware.org@servername1.melanie2.i2
|
|
-> servername1.melanie2.i2
|
|
|
|
If only one server name is found by applying the regex on every value of the
|
|
attribute, then this name is the IMAP/SMTP server for that user.
|
|
Note that this is the case when we got a unique (well formed) value for the
|
|
attribute.
|
|
If the regex finds more than one servername when applied to the differents
|
|
values, then the IMAP/SMTP server name is to be found in the
|
|
mineqMelServeurPrincipal attribute of the user.
|
|
*/
|
|
NSString *server;
|
|
NGLdapEntry *entry;
|
|
|
|
if ((entry = [self _fetchEntryForAgenorUID:_uid]) == nil)
|
|
return nil;
|
|
|
|
if ((server = [self serverFromEntry:entry]) != nil)
|
|
return server;
|
|
|
|
[self debugWithFormat:
|
|
@"%s no chance of getting at server info for user '%@', "
|
|
@"tried everything. Sorry.",
|
|
__PRETTY_FUNCTION__, _uid];
|
|
return nil;
|
|
}
|
|
|
|
- (NSString *)getServerForUID:(NSString *)_uid {
|
|
NSString *server;
|
|
|
|
if (_uid == nil || [_uid length] == 0)
|
|
return nil;
|
|
|
|
if ((server = [self _cachedServerForUID:_uid]) != nil)
|
|
return server;
|
|
|
|
if (useLDAP)
|
|
server = [self primaryGetServerForAgenorUID:_uid];
|
|
else if (fallbackIMAP4Server != nil)
|
|
server = fallbackIMAP4Server;
|
|
else {
|
|
[self logWithFormat:@"ERROR: could not get server for uid '%@', "
|
|
@"neither LDAP (SOGoUserManagerUsesLDAP) nor "
|
|
@"a fallback (SOGoFallbackIMAP4Server) is configured.",
|
|
_uid];
|
|
server = nil;
|
|
}
|
|
|
|
[self _cacheServer:server forUID:_uid];
|
|
return server;
|
|
}
|
|
|
|
/* shared mailboxes */
|
|
|
|
- (NSArray *)getSharedMailboxAccountStringsForUID:(NSString *)_uid {
|
|
NSArray *k;
|
|
|
|
k = [[self getSharedMailboxesAndEMailsForUID:_uid] allKeys];
|
|
|
|
/* ensure that ordering is always the same */
|
|
return [k sortedArrayUsingSelector:@selector(compare:)];
|
|
}
|
|
|
|
- (NSString *)emissionEMailFromEntry:(NGLdapEntry *)_entry {
|
|
id emissionAttr;
|
|
|
|
emissionAttr = [_entry attributeWithName:mailEmissionAttrName];
|
|
if ([emissionAttr count] == 0) {
|
|
[self logWithFormat:@"WARNING: share has no %@ attr: %@",
|
|
mailEmissionAttrName, [_entry dn]];
|
|
return nil;
|
|
}
|
|
|
|
if ([emissionAttr count] > 1) {
|
|
[self logWithFormat:
|
|
@"WARNING: share has more than one value in %@ attr: %@",
|
|
mailEmissionAttrName, [_entry dn]];
|
|
return nil;
|
|
}
|
|
|
|
return [emissionAttr stringValueAtIndex:0];
|
|
}
|
|
|
|
- (NSArray *)getSharedMailboxEMailsForUID:(NSString *)_uid {
|
|
NSMutableArray *shares = nil;
|
|
NGLdapConnection *conn;
|
|
EOQualifier *q;
|
|
NSString *gPattern, *cPattern;
|
|
NSEnumerator *resultEnum;
|
|
NGLdapEntry *entry;
|
|
|
|
if ([_uid length] == 0)
|
|
return nil;
|
|
|
|
if (!useLDAP) {
|
|
[self logWithFormat:
|
|
@"Note: LDAP access is disabled, returning no shared froms."];
|
|
return nil;
|
|
}
|
|
|
|
/* check cache */
|
|
if ((shares = [shareEMailCache objectForKey:_uid]) != nil)
|
|
return shares;
|
|
|
|
/* G and C mean "emission access" */
|
|
gPattern = [_uid stringByAppendingString:@":G"];
|
|
cPattern = [_uid stringByAppendingString:@":C"];
|
|
|
|
q = [EOQualifier qualifierWithQualifierFormat:
|
|
@"((mineqMelPartages = %@) OR (mineqMelPartages = %@)) "
|
|
@"AND (objectclass = %@)",
|
|
gPattern, cPattern, shareLDAPClass];
|
|
|
|
conn = [self ldapConnection];
|
|
|
|
resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
|
|
qualifier:q
|
|
attributes:fromEMailAttrs];
|
|
|
|
while ((entry = [resultEnum nextObject]) != nil) {
|
|
NSString *emissionAttr;
|
|
|
|
if ((emissionAttr = [self emissionEMailFromEntry:entry]) == nil)
|
|
continue;
|
|
|
|
if (shares == nil) shares = [NSMutableArray arrayWithCapacity:4];
|
|
[shares addObject:emissionAttr];
|
|
}
|
|
|
|
/* ensure that ordering is always the same */
|
|
[shares sortUsingSelector:@selector(compare:)];
|
|
|
|
/* cache */
|
|
shares = (shares == nil) ? [NSArray array] : [[shares copy] autorelease];
|
|
[shareEMailCache setObject:shares forKey:_uid];
|
|
return shares;
|
|
}
|
|
|
|
/* identities */
|
|
|
|
- (BOOL)hasUser:(NSString *)_uid partageAccess:(char *)_rightset
|
|
inEntry:(NGLdapEntry *)_entry
|
|
{
|
|
NGLdapAttribute *attr;
|
|
unsigned i, count;
|
|
|
|
attr = [_entry attributeWithName:@"mineqMelPartages"];
|
|
if ((count = [attr count]) == 0) {
|
|
[self logWithFormat:@"WARNING: share has no 'mineqMelPartages' attr: %@",
|
|
[_entry dn]];
|
|
return NO;
|
|
}
|
|
|
|
for (i = 0; i < count; i++) {
|
|
NSString *p;
|
|
NSRange r;
|
|
unichar c;
|
|
register unsigned j;
|
|
|
|
p = [attr stringValueAtIndex:i];
|
|
r = [p rangeOfString:@":"];
|
|
if (r.length == 0) {
|
|
[self errorWithFormat:@"Invalid mineqMelPartages value: '%@'", p];
|
|
continue;
|
|
}
|
|
|
|
/* check whether prefix matches, eg: "helian.h:G" */
|
|
if (r.location != [_uid length]) /* check length */
|
|
continue;
|
|
if (![p hasPrefix:_uid])
|
|
continue;
|
|
|
|
c = [p characterAtIndex:(r.location + r.length)];
|
|
|
|
/* check whether permissions match */
|
|
for (j = 0; _rightset[j] != '\0'; j++) {
|
|
if (c == _rightset[j])
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (NSDictionary *)getSharedMailboxesAndEMailsForUID:(NSString *)_uid {
|
|
/*
|
|
Sample:
|
|
"(&(mineqMelPartages=guizmo.g:*)(objectclass=mineqMelBoite))"
|
|
"guizmo.g" is the uid of the user
|
|
|
|
Login:
|
|
guizmo.g.-.baluh.hommes.tests-montee-en-charge-ogo
|
|
(uid + ".-." + share-uid)
|
|
|
|
Note: shared mailboxes can be on different hosts!
|
|
|
|
This returns a dictionary where the keys are the IMAP4 connect strings
|
|
while the values are the emitter addresses for the box or NSNull if the
|
|
uid is not allowed to emit for this box.
|
|
*/
|
|
NSMutableDictionary *shares = nil;
|
|
NGLdapConnection *conn;
|
|
EOQualifier *q;
|
|
NSString *sharePattern;
|
|
NSEnumerator *resultEnum;
|
|
NGLdapEntry *entry;
|
|
|
|
if ([_uid length] == 0)
|
|
return nil;
|
|
|
|
if (!useLDAP) {
|
|
[self logWithFormat:
|
|
@"Note: LDAP access is disabled, returning no shared accounts."];
|
|
return nil;
|
|
}
|
|
|
|
/* check cache */
|
|
if ((shares = [shareStoreCache objectForKey:_uid]) != nil)
|
|
return shares;
|
|
|
|
sharePattern = [_uid stringByAppendingString:@":*"];
|
|
|
|
q = [EOQualifier qualifierWithQualifierFormat:
|
|
@"(mineqMelPartages = %@) AND (objectclass = %@)",
|
|
sharePattern, shareLDAPClass];
|
|
|
|
conn = [self ldapConnection];
|
|
|
|
resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
|
|
qualifier:q
|
|
attributes:[self mailServerDiscoveryAttributes]];
|
|
|
|
while ((entry = [resultEnum nextObject]) != nil) {
|
|
NSString *server, *shareLogin;
|
|
id shareUid, emitterAddress;
|
|
|
|
/* calculate server connect string */
|
|
|
|
if ([(server = [self serverFromEntry:entry]) length] == 0) {
|
|
[self errorWithFormat:@"found no mail server host for share: %@",
|
|
[entry dn]];
|
|
continue;
|
|
}
|
|
|
|
shareUid = [entry attributeWithName:@"uid"];
|
|
if ([shareUid count] < 1) {
|
|
[self errorWithFormat:@"found no 'uid' for share: %@", [entry dn]];
|
|
continue;
|
|
}
|
|
shareUid = [shareUid stringValueAtIndex:0];
|
|
|
|
shareLogin = [_uid stringByAppendingString:shareLoginSeparator];
|
|
shareLogin = [shareLogin stringByAppendingString:shareUid];
|
|
|
|
if (shares == nil)
|
|
shares = [NSMutableDictionary dictionaryWithCapacity:4];
|
|
|
|
shareLogin = [shareLogin stringByAppendingString:@"@"];
|
|
shareLogin = [shareLogin stringByAppendingString:server];
|
|
|
|
/* calculate emitter address (check for proper access right) */
|
|
|
|
if ([self hasUser:_uid partageAccess:"GC" inEntry:entry])
|
|
emitterAddress = [self emissionEMailFromEntry:entry];
|
|
else
|
|
emitterAddress = [NSNull null];
|
|
|
|
/* set value */
|
|
|
|
[shares setObject: emitterAddress
|
|
forKey: shareLogin];
|
|
}
|
|
|
|
/* cache */
|
|
shares = (shares == nil)
|
|
? [NSDictionary dictionary]
|
|
: [[shares copy] autorelease];
|
|
[shareStoreCache setObject:shares forKey:_uid];
|
|
return shares;
|
|
}
|
|
|
|
/* free busy */
|
|
|
|
- (NSURL *)getFreeBusyURLForUID:(NSString *)_uid {
|
|
[self logWithFormat:@"TODO(%s): implement", __PRETTY_FUNCTION__];
|
|
return nil;
|
|
}
|
|
|
|
/* defaults */
|
|
|
|
- (NSUserDefaults *) _getUserDefaultsForUID: (NSString *) uid
|
|
fieldName: (NSString *) fieldName
|
|
{
|
|
id defaults;
|
|
|
|
if (AgenorProfileURL)
|
|
{
|
|
/* Note: do not cache, otherwise updates can be quite tricky */
|
|
defaults = [[AgenorUserDefaults alloc] initWithTableURL: AgenorProfileURL
|
|
uid: uid fieldName: fieldName];
|
|
[defaults autorelease];
|
|
}
|
|
else
|
|
{
|
|
[self warnWithFormat:
|
|
@"no profile configured, cannot retrieve defaults for user: '%@'",
|
|
uid];
|
|
return defaults = nil;
|
|
}
|
|
|
|
return defaults;
|
|
}
|
|
|
|
- (NSUserDefaults *) getUserDefaultsForUID: (NSString *) uid
|
|
{
|
|
return [self _getUserDefaultsForUID: uid fieldName: @"defaults"];
|
|
}
|
|
|
|
- (NSUserDefaults *) getUserSettingsForUID: (NSString *) uid
|
|
{
|
|
return [self _getUserDefaultsForUID: uid fieldName: @"settings"];
|
|
}
|
|
|
|
/* internet access lock */
|
|
|
|
- (BOOL)isUserAllowedToChangeSOGoInternetAccess:(NSString *)_uid {
|
|
NSNumber *bv;
|
|
|
|
bv = [changeInternetAccessCache objectForKey:_uid];
|
|
if (!bv) {
|
|
BOOL value;
|
|
|
|
value = [self primaryIsUserAllowedToChangeSOGoInternetAccess:_uid];
|
|
bv = [NSNumber numberWithBool:value];
|
|
[changeInternetAccessCache setObject:bv forKey:_uid];
|
|
}
|
|
return [bv boolValue];
|
|
}
|
|
|
|
- (BOOL)primaryIsUserAllowedToChangeSOGoInternetAccess:(NSString *)_uid {
|
|
static NSArray *attrs = nil;
|
|
NGLdapConnection *conn;
|
|
EOQualifier *q;
|
|
NSEnumerator *resultEnum;
|
|
NGLdapEntry *entry;
|
|
NGLdapAttribute *attr;
|
|
NSString *value;
|
|
|
|
if (attrs == nil)
|
|
attrs = [[NSArray alloc] initWithObjects:changeInternetAccessAttrName, nil];
|
|
|
|
q = [EOQualifier qualifierWithQualifierFormat: qualifierFormat, _uid];
|
|
|
|
conn = [self ldapConnection];
|
|
resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
|
|
qualifier:q
|
|
attributes:attrs];
|
|
entry = [resultEnum nextObject];
|
|
if (entry == nil) {
|
|
if(debugOn) {
|
|
[self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
|
|
__PRETTY_FUNCTION__,
|
|
_uid];
|
|
}
|
|
return NO;
|
|
}
|
|
attr = [entry attributeWithName:changeInternetAccessAttrName];
|
|
if(attr == nil && debugOn) {
|
|
[self logWithFormat:@"%s LDAP entry for uid '%@' "
|
|
@"has no mineqOgoAccesInternet attribute?",
|
|
__PRETTY_FUNCTION__,
|
|
_uid];
|
|
return NO; /* nothing we can do about it */
|
|
}
|
|
value = [attr stringValueAtIndex:0];
|
|
return [value boolValue];
|
|
}
|
|
|
|
- (BOOL) isInternetAutoresponderEnabledForUser: (NSString *) _uid
|
|
{
|
|
NSNumber *bv;
|
|
BOOL value;
|
|
|
|
bv = [internetAutoresponderFlagCache objectForKey:_uid];
|
|
if (!bv) {
|
|
value = [self primaryIsInternetAutoresponderEnabledForUser:_uid];
|
|
bv = [NSNumber numberWithBool:value];
|
|
[internetAutoresponderFlagCache setObject:bv forKey:_uid];
|
|
}
|
|
return [bv boolValue];
|
|
}
|
|
|
|
- (BOOL) primaryIsInternetAutoresponderEnabledForUser: (NSString *) _uid
|
|
{
|
|
NGLdapAttribute *attr;
|
|
|
|
attr = [self primaryGetMailAutoresponderAttribute:_uid];
|
|
if (!attr) return NO;
|
|
|
|
return [self isAutoresponderEnabledForAttribute: attr
|
|
matchingPrefix: @"60~"];
|
|
}
|
|
|
|
- (BOOL) isIntranetAutoresponderEnabledForUser: (NSString *) _uid
|
|
{
|
|
NSNumber *bv;
|
|
BOOL value;
|
|
|
|
bv = [intranetAutoresponderFlagCache objectForKey:_uid];
|
|
if (!bv)
|
|
{
|
|
value = [self primaryIsIntranetAutoresponderEnabledForUser:_uid];
|
|
bv = [NSNumber numberWithBool:value];
|
|
[intranetAutoresponderFlagCache setObject:bv forKey:_uid];
|
|
}
|
|
|
|
return [bv boolValue];
|
|
}
|
|
|
|
- (BOOL) primaryIsIntranetAutoresponderEnabledForUser: (NSString *) _uid
|
|
{
|
|
NGLdapAttribute *attr;
|
|
|
|
attr = [self primaryGetMailAutoresponderAttribute:_uid];
|
|
if (!attr) return NO;
|
|
return [self isAutoresponderEnabledForAttribute:attr matchingPrefix:@"50~"];
|
|
}
|
|
|
|
- (BOOL) isAutoresponderEnabledForAttribute: (NGLdapAttribute *)_attr
|
|
matchingPrefix: (NSString *)_prefix
|
|
{
|
|
unsigned i, count;
|
|
|
|
count = [_attr count];
|
|
for (i = 0; i < count; i++) {
|
|
NSString *value;
|
|
|
|
value = [_attr stringValueAtIndex:i];
|
|
if ([value hasPrefix:_prefix]) {
|
|
if ([value rangeOfString:@"DFIN:0"].length > 0)
|
|
return NO;
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (NGLdapAttribute *) primaryGetMailAutoresponderAttribute: (NSString *) _uid
|
|
{
|
|
static NSArray *attrs = nil;
|
|
NGLdapConnection *conn;
|
|
EOQualifier *q;
|
|
NSEnumerator *resultEnum;
|
|
NGLdapEntry *entry;
|
|
NGLdapAttribute *attr;
|
|
|
|
if (attrs == nil)
|
|
attrs = [[NSArray alloc] initWithObjects:mailAutoresponderAttrName, nil];
|
|
|
|
q = [EOQualifier qualifierWithQualifierFormat: qualifierFormat, _uid];
|
|
|
|
conn = [self ldapConnection];
|
|
resultEnum = [conn deepSearchAtBaseDN:ldapBaseDN
|
|
qualifier:q
|
|
attributes:attrs];
|
|
entry = [resultEnum nextObject];
|
|
if (entry == nil) {
|
|
if(debugOn) {
|
|
[self logWithFormat:@"%s Didn't find LDAP entry for uid '%@'!",
|
|
__PRETTY_FUNCTION__,
|
|
_uid];
|
|
}
|
|
return nil;
|
|
}
|
|
attr = [entry attributeWithName:mailAutoresponderAttrName];
|
|
return attr;
|
|
}
|
|
|
|
- (iCalPerson *) iCalPersonWithUid: (NSString *) uid
|
|
{
|
|
iCalPerson *person;
|
|
|
|
person = [iCalPerson new];
|
|
[person autorelease];
|
|
[person setCn: [self getCNForUID: uid]];
|
|
[person setEmail: [self getEmailForUID: uid]];
|
|
|
|
return person;
|
|
}
|
|
|
|
/* debugging */
|
|
|
|
- (BOOL)isDebuggingEnabled {
|
|
return debugOn;
|
|
}
|
|
|
|
@end /* AgenorUserManager */
|