diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index c26242c26..45b1b9820 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/NEWS b/NEWS index f54898315..55e66144a 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,9 @@ Enhancements - updated CKEditor to version 4.1.1 (#2333) + - new failed login attemps rate-limiting options. See the new + SOGoMaximumFailedLoginCount, SOGoMaximumFailedLoginInterval and + SOGoFailedLoginBlockInterval defaults Bug fixes - Fixed decoding of the charset parameter when using single quotes (#2306) diff --git a/SoObjects/SOGo/NSCalendarDate+SOGo.m b/SoObjects/SOGo/NSCalendarDate+SOGo.m index 2b1306ef7..4ba4cd4ed 100644 --- a/SoObjects/SOGo/NSCalendarDate+SOGo.m +++ b/SoObjects/SOGo/NSCalendarDate+SOGo.m @@ -113,19 +113,10 @@ static NSString *rfc822Months[] = {@"", @"Jan", @"Feb", @"Mar", @"Apr", timeZoneShift]; } -/* <<<<<<< variant A -#define secondsOfDistantFuture 1073741823.0 -#define secondsOfDistantPast -1518491648.0 ->>>>>>> variant B */ + #define secondsOfDistantFuture 1073741823.0 #define secondsOfDistantPast -1073741823.0 -/* -####### Ancestor -#define secondsOfDistantFuture 63113990400.0 -#define secondsOfDistantPast -63113817600.0 -======= end */ - + (id) distantFuture { static NSCalendarDate *date = nil; diff --git a/SoObjects/SOGo/SOGoCache.h b/SoObjects/SOGo/SOGoCache.h index b1277937b..ce1d5356c 100644 --- a/SoObjects/SOGo/SOGoCache.h +++ b/SoObjects/SOGo/SOGoCache.h @@ -1,6 +1,6 @@ /* SOGoCache.h - this file is part of SOGo * - * Copyright (C) 2008-2011 Inverse inc. + * Copyright (C) 2008-2013 Inverse inc. * * Author: Wolfgang Sourdeau * Ludovic Marcotte @@ -98,6 +98,11 @@ forLogin: (NSString *) login; - (NSString *) userSettingsForLogin: (NSString *) theLogin; +- (void) setFailedCount: (int) theCount + forLogin: (NSString *) theLogin; + +- (NSDictionary *) failedCountForLogin: (NSString *) login; + // // CAS support // diff --git a/SoObjects/SOGo/SOGoCache.m b/SoObjects/SOGo/SOGoCache.m index e815224e1..c7b1fc6d4 100644 --- a/SoObjects/SOGo/SOGoCache.m +++ b/SoObjects/SOGo/SOGoCache.m @@ -1,6 +1,6 @@ /* SOGoCache.m - this file is part of SOGo * - * Copyright (C) 2008-2010 Inverse inc. + * Copyright (C) 2008-2013 Inverse inc. * * Author: Wolfgang Sourdeau * Ludovic Marcotte @@ -40,6 +40,7 @@ * cas-ticket:< > value = * cas-pgtiou:< > value = * session:< > value = + * +failedlogins value = NSDictionary instance holding the failed count and the date of the first failed authentication */ @@ -47,6 +48,7 @@ #import #import #import +#import #import #import @@ -327,6 +329,9 @@ static memcached_st *handle = NULL; " no handle exists"), key]; } +// +// +// - (void) setValue: (NSString *) value forKey: (NSString *) key { @@ -334,6 +339,9 @@ static memcached_st *handle = NULL; expire: cleanupInterval]; } +// +// +// - (NSString *) valueForKey: (NSString *) key { NSString *valueString; @@ -372,6 +380,9 @@ static memcached_st *handle = NULL; return valueString; } +// +// +// - (void) removeValueForKey: (NSString *) key { NSData *keyData; @@ -420,6 +431,9 @@ static memcached_st *handle = NULL; } } +// +// +// - (NSString *) _valuesOfType: (NSString *) theType forKey: (NSString *) theKey { @@ -439,6 +453,9 @@ static memcached_st *handle = NULL; return valueString; } +// +// +// - (void) setUserAttributes: (NSString *) theAttributes forLogin: (NSString *) login { @@ -475,6 +492,60 @@ static memcached_st *handle = NULL; return [self _valuesOfType: @"settings" forKey: theLogin]; } +// +// SOGo password failed counts +// +- (void) setFailedCount: (int) theCount + forLogin: (NSString *) theLogin +{ + NSMutableDictionary *d; + NSNumber *count; + + if (theCount) + { + count = [NSNumber numberWithInt: theCount]; + + d = [NSMutableDictionary dictionaryWithDictionary: [self failedCountForLogin: theLogin]]; + + if (![d objectForKey: @"InitialDate"]) + { + [d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"InitialDate"]; + } + + [d setObject: count forKey: @"FailedCount"]; + [self _cacheValues: [d jsonRepresentation] + ofType: @"failedlogins" + forKey: theLogin]; + } + else + { + [self removeValueForKey: [NSString stringWithFormat: @"%@+failedlogins", theLogin]]; + } +} + +// +// Returns a dictionary with two keys/values +// +// FailedCount -> +// InitialDate -> +// +- (NSDictionary *) failedCountForLogin: (NSString *) theLogin +{ + NSDictionary *d; + NSString *s; + + s = [self _valuesOfType: @"failedlogins" forKey: theLogin]; + d = nil; + + if (s) + { + d = [s objectFromJSONString]; + } + + return d; +} + + // // CAS session support // diff --git a/SoObjects/SOGo/SOGoSystemDefaults.h b/SoObjects/SOGo/SOGoSystemDefaults.h index 2057f24b0..f2c2feea0 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.h +++ b/SoObjects/SOGo/SOGoSystemDefaults.h @@ -1,6 +1,6 @@ /* SOGoSystemDefaults.h - this file is part of SOGo * - * Copyright (C) 2009-2011 Inverse inc. + * Copyright (C) 2009-2013 Inverse inc. * * Author: Wolfgang Sourdeau * Francis Lachapelle @@ -86,6 +86,10 @@ - (BOOL) enablePublicAccess; +- (int) maximumFailedLoginCount; +- (int) maximumFailedLoginInterval; +- (int) failedLoginBlockInternval; + @end #endif /* SOGOSYSTEMDEFAULTS_H */ diff --git a/SoObjects/SOGo/SOGoSystemDefaults.m b/SoObjects/SOGo/SOGoSystemDefaults.m index e724c4fb9..07264eb64 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.m +++ b/SoObjects/SOGo/SOGoSystemDefaults.m @@ -1,6 +1,6 @@ /* SOGoSystemDefaults.m - this file is part of SOGo * - * Copyright (C) 2009-2012 Inverse inc. + * Copyright (C) 2009-2013 Inverse inc. * Copyright (C) 2012 Jeroen Dekkers * * Author: Wolfgang Sourdeau @@ -513,4 +513,33 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict, return [self boolForKey: @"SOGoEnablePublicAccess"]; } +- (int) maximumFailedLoginCount +{ + return [self integerForKey: @"SOGoMaximumFailedLoginCount"]; +} + +- (int) maximumFailedLoginInterval +{ + int v; + + v = [self integerForKey: @"SOGoMaximumFailedLoginInterval"]; + + if (!v) + v = 10; + + return v; +} + +- (int) failedLoginBlockInterval +{ + int v; + + v = [self integerForKey: @"SOGoFailedLoginBlockInterval"]; + + if (!v) + v = 300; + + return v; +} + @end diff --git a/SoObjects/SOGo/SOGoUserManager.m b/SoObjects/SOGo/SOGoUserManager.m index d6a0f9a5b..aa320b168 100644 --- a/SoObjects/SOGo/SOGoUserManager.m +++ b/SoObjects/SOGo/SOGoUserManager.m @@ -447,6 +447,9 @@ static Class NSNullK; return checkOK; } +// +// +// - (BOOL) checkLogin: (NSString *) _login password: (NSString *) _pwd domain: (NSString **) _domain @@ -463,6 +466,9 @@ static Class NSNullK; useCache: YES]; } +// +// +// - (BOOL) checkLogin: (NSString *) _login password: (NSString *) _pwd domain: (NSString **) _domain @@ -471,8 +477,9 @@ static Class NSNullK; grace: (int *) _grace useCache: (BOOL) useCache { + NSMutableDictionary *currentUser, *failedCount; NSString *dictPassword, *username, *jsonUser; - NSMutableDictionary *currentUser; + SOGoSystemDefaults *dd; BOOL checkOK; // We check for cached passwords. If the entry is cached, we @@ -482,6 +489,41 @@ static Class NSNullK; username = [NSString stringWithFormat: @"%@@%@", _login, *_domain]; else username = _login; + + failedCount = [[SOGoCache sharedCache] failedCountForLogin: username]; + dd = [SOGoSystemDefaults sharedSystemDefaults]; + + // + // We check the fail count per user in memcache (per server). If the + // fail count reaches X in Y minutes, we deny immediately the + // authentications for Z minutes + // + if (failedCount) + { + unsigned int current_time, start_time, delta, block_time; + + current_time = [[NSCalendarDate date] timeIntervalSince1970]; + start_time = [[failedCount objectForKey: @"InitialDate"] unsignedIntValue]; + delta = current_time - start_time; + + block_time = [dd failedLoginBlockInterval]; + + if ([[failedCount objectForKey: @"FailedCount"] intValue] >= [dd maximumFailedLoginCount] && + delta >= [dd maximumFailedLoginInterval] && + delta <= block_time ) + { + *_perr = PolicyAccountLocked; + return NO; + } + + if (delta > block_time) + { + [[SOGoCache sharedCache] setFailedCount: 0 + forLogin: username]; + } + } + + jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: username]; currentUser = [jsonUser objectFromJSONString]; dictPassword = [currentUser objectForKey: @"password"]; @@ -514,7 +556,16 @@ static Class NSNullK; forLogin: username]; } else - checkOK = NO; + { + // If failed login "rate-limiting" is enabled, we adjust the stats + if ([dd maximumFailedLoginCount]) + { + [[SOGoCache sharedCache] setFailedCount: ([[failedCount objectForKey: @"FailedCount"] intValue] + 1) + forLogin: username]; + } + + checkOK = NO; + } // We MUST, for all LDAP sources, update the bindDN and bindPassword // to the user's value if bindAsCurrentUser is set to true in the @@ -538,6 +589,9 @@ static Class NSNullK; return checkOK; } +// +// +// - (BOOL) changePasswordForLogin: (NSString *) login inDomain: (NSString *) domain oldPassword: (NSString *) oldPassword diff --git a/UI/MainUI/SOGoRootPage.m b/UI/MainUI/SOGoRootPage.m index fb349ef61..337d35e7e 100644 --- a/UI/MainUI/SOGoRootPage.m +++ b/UI/MainUI/SOGoRootPage.m @@ -172,6 +172,9 @@ andJSONRepresentation: jsonError]; } +// +// +// - (id ) connectAction { WOResponse *response;