Added new failed login rate-limiting options

pull/12/head
Ludovic Marcotte 2013-06-11 09:41:17 -04:00
parent 2a51f6f385
commit cfee5aa3f4
9 changed files with 176 additions and 16 deletions

3
NEWS
View File

@ -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)

View File

@ -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;

View File

@ -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 <wsourdeau@inverse.ca>
* Ludovic Marcotte <lmarcotte@inverse.ca>
@ -98,6 +98,11 @@
forLogin: (NSString *) login;
- (NSString *) userSettingsForLogin: (NSString *) theLogin;
- (void) setFailedCount: (int) theCount
forLogin: (NSString *) theLogin;
- (NSDictionary *) failedCountForLogin: (NSString *) login;
//
// CAS support
//

View File

@ -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 <wsourdeau@inverse.ca>
* Ludovic Marcotte <lmarcotte@inverse.ca>
@ -40,6 +40,7 @@
* cas-ticket:< > value =
* cas-pgtiou:< > value =
* session:< > value =
* <uid>+failedlogins value = NSDictionary instance holding the failed count and the date of the first failed authentication
*/
@ -47,6 +48,7 @@
#import <Foundation/NSData.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSLock.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSString.h>
#import <Foundation/NSTimer.h>
@ -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
//

View File

@ -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 <wsourdeau@inverse.ca>
* Francis Lachapelle <flachapelle@inverse.ca>
@ -86,6 +86,10 @@
- (BOOL) enablePublicAccess;
- (int) maximumFailedLoginCount;
- (int) maximumFailedLoginInterval;
- (int) failedLoginBlockInternval;
@end
#endif /* SOGOSYSTEMDEFAULTS_H */

View File

@ -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 <jeroen@dekkers.ch>
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
@ -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

View File

@ -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

View File

@ -172,6 +172,9 @@
andJSONRepresentation: jsonError];
}
//
//
//
- (id <WOActionResults>) connectAction
{
WOResponse *response;