/* SQLSource.h - this file is part of SOGo * * Copyright (C) 2009 Inverse inc. * * Author: Ludovic Marcotte * * 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 #import #import #import #import #import #import #import #import #import #import #import #include #include #include "SQLSource.h" /** * The view MUST contain the following columns: * * c_uid - will be used for authentication - it's a username or username@domain.tld) * c_name - which can be identical to c_uid - will be used to uniquely identify entries) * c_password - password of the user, plain-text or md5 encoded for now * c_cn - the user's common name * mail - hte user's mail address * * Other columns can be defined - see LDAPSource.m for the complete list. * * * A SQL source can be defined like this: * * { * id = zot; * type = sql; * viewURL = "http://sogo:sogo@127.0.0.1:5432/sogo/sogo_view"; * canAuthenticate = YES; * isAddressBook = YES; * userPasswordAlgorithm = md5; * } * */ @implementation SQLSource + (id) sourceFromUDSource: (NSDictionary *) udSource inDomain: (NSString *) domain { return [[[self alloc] initFromUDSource: udSource inDomain: domain] autorelease]; } - (id) init { if ((self = [super init])) { _sourceID = nil; _mailFields = nil; _userPasswordAlgorithm = nil; _viewURL = nil; } return self; } - (void) dealloc { [_sourceID release]; [_mailFields release]; [_userPasswordAlgorithm release]; [_viewURL release]; [super dealloc]; } - (id) initFromUDSource: (NSDictionary *) udSource inDomain: (NSString *) sourceDomain { self = [self init]; ASSIGN(_sourceID, [udSource objectForKey: @"id"]); ASSIGN(_mailFields, [udSource objectForKey: @"MailFieldNames"]); ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]); if (!_userPasswordAlgorithm) _userPasswordAlgorithm = @"none"; if ([udSource objectForKey: @"viewURL"]) _viewURL = [[NSURL alloc] initWithString: [udSource objectForKey: @"viewURL"]]; #warning this domain code has no effect yet if ([sourceDomain length]) ASSIGN (_domain, sourceDomain); if (!_viewURL) { [self autorelease]; return nil; } return self; } - (NSString *) domain { return _domain; } - (BOOL) _isPassword: (NSString *) plainPassword equalTo: (NSString *) encryptedPassword { if ([_userPasswordAlgorithm caseInsensitiveCompare: @"none"] == NSOrderedSame) { return [plainPassword isEqualToString: encryptedPassword]; } else if ([_userPasswordAlgorithm caseInsensitiveCompare: @"md5"] == NSOrderedSame) { NSString *s; unsigned char md[MD5_DIGEST_LENGTH]; char buf[80]; int i; memset(md, 0, MD5_DIGEST_LENGTH); memset(buf, 0, 80); EVP_Digest([plainPassword UTF8String], strlen([plainPassword UTF8String]), md, NULL, EVP_md5(), NULL); for (i = 0; i < MD5_DIGEST_LENGTH; i++) sprintf(&(buf[i*2]), "%02x", md[i]); s = [NSString stringWithUTF8String: buf]; return [s isEqualToString: encryptedPassword]; } [self errorWithFormat: @"Unsupported user-password algorithm: %@", _userPasswordAlgorithm]; return NO; } - (BOOL) checkLogin: (NSString *) login andPassword: (NSString *) password { EOAdaptorChannel *channel; GCSChannelManager *cm; NSException *ex; NSString *sql; BOOL rc; rc = NO; cm = [GCSChannelManager defaultChannelManager]; channel = [cm acquireOpenChannelForURL: _viewURL]; if (channel) { sql = [NSString stringWithFormat: (@"SELECT c_password" @" FROM %@" @" WHERE c_uid = '%@'"), [_viewURL gcsTableName], login]; ex = [channel evaluateExpressionX: sql]; if (!ex) { NSDictionary *row; NSArray *attrs; NSString *value; attrs = [channel describeResults: NO]; row = [channel fetchAttributes: attrs withZone: NULL]; value = [row objectForKey: @"c_password"]; rc = [self _isPassword: password equalTo: value]; [channel cancelFetch]; } else [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; [cm releaseChannel: channel]; } else [self errorWithFormat:@"failed to acquire channel for URL: %@", [_viewURL absoluteString]]; return rc; } - (NSDictionary *) _lookupContactEntry: (NSString *) theID considerEmail: (BOOL) b { EOAdaptorChannel *channel; GCSChannelManager *cm; NSException *ex; NSString *sql; NSMutableDictionary *response; response = nil; cm = [GCSChannelManager defaultChannelManager]; channel = [cm acquireOpenChannelForURL: _viewURL]; if (channel) { if (!b) sql = [NSString stringWithFormat: (@"SELECT *" @" FROM %@" @" WHERE c_uid = '%@'"), [_viewURL gcsTableName], theID]; else sql = [NSString stringWithFormat: (@"SELECT *" @" FROM %@" @" WHERE c_uid = '%@' OR" @" LOWER(mail) = '%@'"), [_viewURL gcsTableName], theID, [theID lowercaseString]]; ex = [channel evaluateExpressionX: sql]; if (!ex) { response = [[channel fetchAttributes: [channel describeResults: NO] withZone: NULL] mutableCopy]; [response autorelease]; [channel cancelFetch]; // We have to do this here since we do not manage modules // constraints right now over a SQL backend. [response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"]; [response setObject: [NSNumber numberWithBool: YES] forKey: @"MailAccess"]; } else [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; [cm releaseChannel: channel]; } else [self errorWithFormat:@"failed to acquire channel for URL: %@", [_viewURL absoluteString]]; return response; } - (NSDictionary *) lookupContactEntry: (NSString *) theID { return [self _lookupContactEntry: theID considerEmail: NO]; } - (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) entryID { return [self _lookupContactEntry: entryID considerEmail: YES]; } - (NSArray *) allEntryIDs { EOAdaptorChannel *channel; NSMutableArray *results; GCSChannelManager *cm; NSException *ex; NSString *sql; results = [NSMutableArray array]; cm = [GCSChannelManager defaultChannelManager]; channel = [cm acquireOpenChannelForURL: _viewURL]; if (channel) { sql = [NSString stringWithFormat: @"SELECT c_uid FROM %@", [_viewURL gcsTableName]]; ex = [channel evaluateExpressionX: sql]; if (!ex) { NSDictionary *row; NSArray *attrs; NSString *value; attrs = [channel describeResults: NO]; while ((row = [channel fetchAttributes: attrs withZone: NULL])) { value = [row objectForKey: @"c_uid"]; if (value) [results addObject: value]; } } else [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; [cm releaseChannel: channel]; } else [self errorWithFormat:@"failed to acquire channel for URL: %@", [_viewURL absoluteString]]; return results; } - (NSArray *) fetchContactsMatching: (NSString *) filter { EOAdaptorChannel *channel; NSMutableArray *results; GCSChannelManager *cm; NSException *ex; NSString *sql, *lowerFilter; results = [NSMutableArray array]; cm = [GCSChannelManager defaultChannelManager]; channel = [cm acquireOpenChannelForURL: _viewURL]; if (channel) { lowerFilter = [filter lowercaseString]; sql = [NSString stringWithFormat: (@"SELECT *" @" FROM %@" @" WHERE LOWER(c_cn) LIKE '%%%@%%'" @" OR LOWER(mail) LIKE '%%%@%%'"), [_viewURL gcsTableName], lowerFilter, lowerFilter]; ex = [channel evaluateExpressionX: sql]; if (!ex) { NSDictionary *row; NSArray *attrs; attrs = [channel describeResults: NO]; while ((row = [channel fetchAttributes: attrs withZone: NULL])) [results addObject: row]; } else [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; [cm releaseChannel: channel]; } else [self errorWithFormat:@"failed to acquire channel for URL: %@", [_viewURL absoluteString]]; return results; } - (NSString *) sourceID { return _sourceID; } @end