From 9d6ab2df3364e8863c94b6a4c4cd2f239399a7f8 Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Fri, 18 Mar 2016 11:03:45 -0400 Subject: [PATCH] (feat) user rate-limiting support for all SOGo requests --- Documentation/SOGoInstallationGuide.asciidoc | 15 +++++ Main/SOGo.m | 58 ++++++++++++++++++++ SoObjects/SOGo/SOGoCache.h | 9 +++ SoObjects/SOGo/SOGoCache.m | 53 ++++++++++++++++++ SoObjects/SOGo/SOGoSystemDefaults.h | 5 ++ SoObjects/SOGo/SOGoSystemDefaults.m | 36 ++++++++++++ 6 files changed, 176 insertions(+) diff --git a/Documentation/SOGoInstallationGuide.asciidoc b/Documentation/SOGoInstallationGuide.asciidoc index 1b53d4f6d..9c87f47c6 100644 --- a/Documentation/SOGoInstallationGuide.asciidoc +++ b/Documentation/SOGoInstallationGuide.asciidoc @@ -715,6 +715,21 @@ Default value is `0`, or disabled. |Number of seconds, default to `300` (or 5 minutes). Note that _SOGoCacheCleanupInterval_ must be set to a value equal or higher than _SOGoFailedLoginBlockInterval_. + +|S |SOGoMaximumRequestCount +|Parameter used to control the number of requests a user can send to the SOGo +server in _SOGoMaximumRequestInterval_ seconds or more. If conditions are met +or exceeded, the user will not be able to perform requests on the SOGo server +for _SOGoRequestBlockInterval_ seconds and will receive 429 HTTP responses for +any requests being made. Default value is 0, or disabled + +|S |SOGoMaximumRequestInterval +|Number of seconds, defaults to `30`. + +|S |SOGoRequestBlockInterval +|Number of seconds, defaults to 300 (or 5 minutes). Note that _SOGoCacheCleanupInterval_ +must be set to a value equal or higher than _SOGoRequestBlockInterval_. + |======================================================================= Authentication using LDAP diff --git a/Main/SOGo.m b/Main/SOGo.m index 8c9777206..fd282cc56 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -476,6 +476,8 @@ static BOOL debugLeaks; static BOOL debugOn = NO; WOResponse *resp; NSDate *startDate; + NSString *path; + NSTimeInterval timeDelta; if (debugRequests) @@ -499,6 +501,62 @@ static BOOL debugLeaks; } #endif + // We check for rate-limiting settings - ignore anything actually + // sent to /SOGo/ (so unauthenticated requests). + path = [_request requestHandlerPath]; + if ([path length]) + { + NSDictionary *requestCount; + NSString *username; + NSRange r; + + r = [path rangeOfString: @"/"]; + username = [path substringWithRange: NSMakeRange(0, r.location)]; + requestCount = [cache requestCountForLogin: username]; + + if (requestCount) + { + SOGoSystemDefaults *sd; + + unsigned int current_time, start_time, delta, block_time, request_count; + + sd = [SOGoSystemDefaults sharedSystemDefaults]; + + current_time = [[NSCalendarDate date] timeIntervalSince1970]; + start_time = [[requestCount objectForKey: @"InitialDate"] unsignedIntValue]; + delta = current_time - start_time; + + block_time = [sd requestBlockInterval]; + request_count = [[requestCount objectForKey: @"RequestCount"] intValue]; + + if ( request_count >= [sd maximumRequestCount] && + delta < [sd maximumRequestInterval] && + delta <= block_time ) + { + resp = [WOResponse responseWithRequest: _request]; + [resp setStatus: 429]; + return resp; + } + + if (delta > block_time) + { + [cache setRequestCount: 1 + forLogin: username + interval: current_time]; + } + else + [cache setRequestCount: (request_count+1) + forLogin: username + interval: start_time]; + } + else + { + [cache setRequestCount: 1 + forLogin: username + interval: 0]; + } + } + resp = [super dispatchRequest: _request]; [cache killCache]; diff --git a/SoObjects/SOGo/SOGoCache.h b/SoObjects/SOGo/SOGoCache.h index a89de5907..b0fcf550b 100644 --- a/SoObjects/SOGo/SOGoCache.h +++ b/SoObjects/SOGo/SOGoCache.h @@ -141,6 +141,15 @@ forPath: (NSString *) thePath; - (NSMutableDictionary *) aclsForPath: (NSString *) thePath; +// +// SOGo rate-limiting +// +- (void) setRequestCount: (int) theCount + forLogin: (NSString *) theLogin + interval: (unsigned int) theInterval; + +- (NSDictionary *) requestCountForLogin: (NSString *) theLogin; + @end #endif /* SOGOCACHE_H */ diff --git a/SoObjects/SOGo/SOGoCache.m b/SoObjects/SOGo/SOGoCache.m index 4045672ed..dcb570cdd 100644 --- a/SoObjects/SOGo/SOGoCache.m +++ b/SoObjects/SOGo/SOGoCache.m @@ -727,5 +727,58 @@ static memcached_st *handle = NULL; } +// +// SOGo request count for rate-limiting +// +- (void) setRequestCount: (int) theCount + forLogin: (NSString *) theLogin + interval: (unsigned int) theInterval +{ + NSMutableDictionary *d; + NSNumber *count; + + if (theCount) + { + count = [NSNumber numberWithInt: theCount]; + + d = [NSMutableDictionary dictionaryWithDictionary: [self requestCountForLogin: theLogin]]; + + if (![d objectForKey: @"InitialDate"] || theInterval == 0) + [d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"InitialDate"]; + else + [d setObject: [NSNumber numberWithUnsignedInt: theInterval] forKey: @"InitialDate"]; + + [d setObject: count forKey: @"RequestCount"]; + [self _cacheValues: [d jsonRepresentation] + ofType: @"requestcount" + forKey: theLogin]; + } + else + { + [self removeValueForKey: [NSString stringWithFormat: @"%@+failedlogins", theLogin]]; + } +} + +// +// Returns a dictionary with two keys/values +// +// RequestCount -> +// InitialDate -> +// +- (NSDictionary *) requestCountForLogin: (NSString *) theLogin +{ + NSDictionary *d; + NSString *s; + + s = [self _valuesOfType: @"requestcount" forKey: theLogin]; + d = nil; + + if (s) + { + d = [s objectFromJSONString]; + } + + return d; +} @end diff --git a/SoObjects/SOGo/SOGoSystemDefaults.h b/SoObjects/SOGo/SOGoSystemDefaults.h index 50064c0de..98f141438 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.h +++ b/SoObjects/SOGo/SOGoSystemDefaults.h @@ -97,6 +97,11 @@ - (int) maximumSubmissionInterval; - (int) messageSubmissionBlockInterval; +- (int) maximumRequestCount; +- (int) maximumRequestInterval; +- (int) requestBlockInterval; + + - (int) maximumPingInterval; - (int) maximumSyncInterval; - (int) internalSyncInterval; diff --git a/SoObjects/SOGo/SOGoSystemDefaults.m b/SoObjects/SOGo/SOGoSystemDefaults.m index 452fe41c6..00bfcef6d 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.m +++ b/SoObjects/SOGo/SOGoSystemDefaults.m @@ -598,6 +598,42 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict, return v; } +// +// SOGo rate-limiting +// +- (int) maximumRequestCount +{ + return [self integerForKey: @"SOGoMaximumRequestCount"]; +} + +- (int) maximumRequestInterval +{ + int v; + + v = [self integerForKey: @"SOGoMaximumRequestInterval"]; + + if (!v) + v = 30; + + return v; +} + +- (int) requestBlockInterval +{ + int v; + + v = [self integerForKey: @"SOGoRequestBlockInterval"]; + + if (!v) + v = 300; + + return v; +} + + +// +// SOGo EAS settings +// - (int) maximumPingInterval { int v;