diff --git a/Documentation/SOGo Installation Guide.odt b/Documentation/SOGo Installation Guide.odt index 45b1b9820..8b1b5faeb 100644 Binary files a/Documentation/SOGo Installation Guide.odt and b/Documentation/SOGo Installation Guide.odt differ diff --git a/NEWS b/NEWS index 55e66144a..334e5d7c2 100644 --- a/NEWS +++ b/NEWS @@ -5,7 +5,10 @@ Enhancements - updated CKEditor to version 4.1.1 (#2333) - new failed login attemps rate-limiting options. See the new SOGoMaximumFailedLoginCount, SOGoMaximumFailedLoginInterval and - SOGoFailedLoginBlockInterval defaults + SOGoFailedLoginBlockInterval defaults + - new message submissions rate-limiting options. See the new + SOGoMaximumMessageSubmissionCount, SOGoMaximumRecipientCount, + SOGoMaximumSubmissionInterval and SOGoMessageSubmissionBlockInterval defaults Bug fixes - Fixed decoding of the charset parameter when using single quotes (#2306) diff --git a/SoObjects/SOGo/SOGoCache.h b/SoObjects/SOGo/SOGoCache.h index ce1d5356c..c8f1f9aa7 100644 --- a/SoObjects/SOGo/SOGoCache.h +++ b/SoObjects/SOGo/SOGoCache.h @@ -103,6 +103,12 @@ - (NSDictionary *) failedCountForLogin: (NSString *) login; +- (void) setMessageSubmissionsCount: (int) theCount + recipientsCount: (int) theRecipientsCount + forLogin: (NSString *) theLogin; + +- (NSDictionary *) messageSubmissionsCountForLogin: (NSString *) theLogin; + // // CAS support // diff --git a/SoObjects/SOGo/SOGoCache.m b/SoObjects/SOGo/SOGoCache.m index c7b1fc6d4..afde2d9b4 100644 --- a/SoObjects/SOGo/SOGoCache.m +++ b/SoObjects/SOGo/SOGoCache.m @@ -24,23 +24,24 @@ /* * [ Cache Structure ] * - * users value = instances of SOGoUser > flushed after the completion of every SOGo requests - * groups value = instances of SOGoGroup > flushed after the completion of every SOGo requests - * imap4Connections value = - * localCache value = any value of what's in memcached - this is used to NOT query memcached within the same sogod instance + * users value = instances of SOGoUser > flushed after the completion of every SOGo requests + * groups value = instances of SOGoGroup > flushed after the completion of every SOGo requests + * imap4Connections value = + * localCache value = any value of what's in memcached - this is used to NOT query memcached within the same sogod instance * * [ Distributed (using memcached) cache structure ] * - * +defaults value = NSDictionary instance > user's defaults - * +settings value = NSDictionary instance > user's settings - * +attributes value = NSMutableDictionary instance > user's LDAP attributes - * +acl value = NSDictionary instance > ACLs on an object at specified path - * + value = NSString instance (array components separated by ",") or group member logins for a specific group in domain - * cas-id:< > value = - * cas-ticket:< > value = - * cas-pgtiou:< > value = - * session:< > value = - * +failedlogins value = NSDictionary instance holding the failed count and the date of the first failed authentication + * +defaults value = NSDictionary instance > user's defaults + * +settings value = NSDictionary instance > user's settings + * +attributes value = NSMutableDictionary instance > user's LDAP attributes + * +acl value = NSDictionary instance > ACLs on an object at specified path + * + value = NSString instance (array components separated by ",") or group member logins for a specific group in domain + * cas-id:< > value = + * cas-ticket:< > value = + * cas-pgtiou:< > value = + * session:< > value = + * +failedlogins value = NSDictionary instance holding the failed count and the date of the first failed authentication + * +messagesubmissions value = NSDictionary instance holding the number of messages sent, and number of recipients */ @@ -545,6 +546,62 @@ static memcached_st *handle = NULL; return d; } +// +// +// +- (void) setMessageSubmissionsCount: (int) theCount + recipientsCount: (int) theRecipientsCount + forLogin: (NSString *) theLogin +{ + NSNumber *messages_count, *recipients_count; + NSMutableDictionary *d; + + if (theCount) + { + messages_count = [NSNumber numberWithInt: theCount]; + recipients_count = [NSNumber numberWithInt: theRecipientsCount]; + + d = [NSMutableDictionary dictionaryWithDictionary: [self messageSubmissionsCountForLogin: theLogin]]; + + if (![d objectForKey: @"InitialDate"]) + { + [d setObject: [NSNumber numberWithUnsignedInt: [[NSCalendarDate date] timeIntervalSince1970]] forKey: @"InitialDate"]; + } + + [d setObject: messages_count forKey: @"MessagesCount"]; + [d setObject: recipients_count forKey: @"RecipientsCount"]; + + [self _cacheValues: [d jsonRepresentation] + ofType: @"messagesubmissions" + forKey: theLogin]; + } + else + { + [self removeValueForKey: [NSString stringWithFormat: @"%@+messagesubmissions", theLogin]]; + } +} + +// +// MessagesCount -> +// RecipientsCount -> +// InitialDate -> +// +- (NSDictionary *) messageSubmissionsCountForLogin: (NSString *) theLogin +{ + NSDictionary *d; + NSString *s; + + s = [self _valuesOfType: @"messagesubmissions" 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 f2c2feea0..cb705f560 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.h +++ b/SoObjects/SOGo/SOGoSystemDefaults.h @@ -88,7 +88,12 @@ - (int) maximumFailedLoginCount; - (int) maximumFailedLoginInterval; -- (int) failedLoginBlockInternval; +- (int) failedLoginBlockInterval; + +- (int) maximumMessageSubmissionCount; +- (int) maximumRecipientCount; +- (int) maximumSubmissionInterval; +- (int) messageSubmissionBlockInterval; @end diff --git a/SoObjects/SOGo/SOGoSystemDefaults.m b/SoObjects/SOGo/SOGoSystemDefaults.m index 07264eb64..0457fae4b 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.m +++ b/SoObjects/SOGo/SOGoSystemDefaults.m @@ -513,6 +513,9 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict, return [self boolForKey: @"SOGoEnablePublicAccess"]; } +// +// +// - (int) maximumFailedLoginCount { return [self integerForKey: @"SOGoMaximumFailedLoginCount"]; @@ -542,4 +545,41 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict, return v; } +// +// +// +- (int) maximumMessageSubmissionCount +{ + return [self integerForKey: @"SOGoMaximumMessageSubmissionCount"]; +} + +- (int) maximumRecipientCount +{ + return [self integerForKey: @"SOGoMaximumRecipientCount"]; +} + +- (int) maximumSubmissionInterval +{ + int v; + + v = [self integerForKey: @"SOGoMaximumSubmissionInterval"]; + + if (!v) + v = 30; + + return v; +} + +- (int) messageSubmissionBlockInterval +{ + int v; + + v = [self integerForKey: @"SOGoMessageSubmissionBlockInterval"]; + + if (!v) + v = 300; + + return v; +} + @end diff --git a/UI/MailerUI/UIxMailEditor.m b/UI/MailerUI/UIxMailEditor.m index 6cc5c5819..0c695bcc8 100644 --- a/UI/MailerUI/UIxMailEditor.m +++ b/UI/MailerUI/UIxMailEditor.m @@ -46,6 +46,8 @@ #import #import +#import +#import #import #import #import @@ -659,12 +661,57 @@ static NSArray *infoKeys = nil; return error; } +// +// +// - (WOResponse *) sendAction { SOGoDraftObject *co; NSDictionary *jsonResponse; NSException *error; NSMutableArray *errorMsg; + NSDictionary *messageSubmissions; + SOGoSystemDefaults *dd; + + int messages_count, recipients_count; + + messageSubmissions = [[SOGoCache sharedCache] messageSubmissionsCountForLogin: [[context activeUser] login]]; + dd = [SOGoSystemDefaults sharedSystemDefaults]; + messages_count = recipients_count = 0; + + if (messageSubmissions) + { + unsigned int current_time, start_time, delta, block_time; + + current_time = [[NSCalendarDate date] timeIntervalSince1970]; + start_time = [[messageSubmissions objectForKey: @"InitialDate"] unsignedIntValue]; + delta = current_time - start_time; + + block_time = [dd messageSubmissionBlockInterval]; + messages_count = [[messageSubmissions objectForKey: @"MessagesCount"] intValue]; + recipients_count = [[messageSubmissions objectForKey: @"RecipientsCount"] intValue]; + + if ((messages_count >= [dd maximumMessageSubmissionCount] || recipients_count >= [dd maximumRecipientCount]) && + delta >= [dd maximumSubmissionInterval] && + delta <= block_time ) + { + jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: + @"failure", @"status", + [self labelForKey: @"Tried to send too many mails. Please wait."], + @"message", + nil]; + return [self responseWithStatus: 200 + andString: [jsonResponse jsonRepresentation]]; + } + + if (delta > block_time) + { + [[SOGoCache sharedCache] setMessageSubmissionsCount: 0 + recipientsCount: 0 + forLogin: [[context activeUser] login]]; + } + + } co = [self clientObject]; @@ -691,11 +738,23 @@ static NSArray *infoKeys = nil; nil]; } else - jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: - @"success", @"status", - [co sourceFolder], @"sourceFolder", - [NSNumber numberWithInt: [co IMAP4ID]], @"messageID", - nil]; + { + jsonResponse = [NSDictionary dictionaryWithObjectsAndKeys: + @"success", @"status", + [co sourceFolder], @"sourceFolder", + [NSNumber numberWithInt: [co IMAP4ID]], @"messageID", + nil]; + + recipients_count += [[co allRecipients] count]; + messages_count += 1; + + if ([dd maximumMessageSubmissionCount] > 0 && [dd maximumRecipientCount] > 0) + { + [[SOGoCache sharedCache] setMessageSubmissionsCount: messages_count + recipientsCount: recipients_count + forLogin: [[context activeUser] login]]; + } + } return [self responseWithStatus: 200 andString: [jsonResponse jsonRepresentation]];