2011-06-29 19:43:10 +02:00
|
|
|
/* SOGoSieveManager.m - this file is part of SOGo
|
2010-03-02 20:57:30 +01:00
|
|
|
*
|
2011-06-29 19:43:10 +02:00
|
|
|
* Copyright (C) 2010-2011 Inverse inc.
|
2010-03-02 20:57:30 +01:00
|
|
|
*
|
|
|
|
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
2011-06-29 19:43:10 +02:00
|
|
|
* Ludovic Marcotte <lmarcotte@inverse.ca>
|
2010-03-02 20:57:30 +01:00
|
|
|
*
|
|
|
|
* 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 <Foundation/NSArray.h>
|
|
|
|
#import <Foundation/NSDictionary.h>
|
|
|
|
#import <Foundation/NSString.h>
|
2011-06-29 19:43:10 +02:00
|
|
|
#import <Foundation/NSURL.h>
|
2010-03-02 20:57:30 +01:00
|
|
|
#import <Foundation/NSValue.h>
|
|
|
|
|
|
|
|
#import <SOGo/NSArray+Utilities.h>
|
|
|
|
#import <SOGo/NSDictionary+Utilities.h>
|
|
|
|
#import <SOGo/SOGoDomainDefaults.h>
|
|
|
|
#import <SOGo/SOGoUser.h>
|
|
|
|
|
2011-06-29 19:43:10 +02:00
|
|
|
#import <NGStreams/NGInternetSocketAddress.h>
|
|
|
|
#import <NGImap4/NGSieveClient.h>
|
2010-03-02 20:57:30 +01:00
|
|
|
|
2011-06-29 19:43:10 +02:00
|
|
|
#import "../Mailer/SOGoMailAccount.h"
|
|
|
|
|
|
|
|
#import "SOGoSieveManager.h"
|
2010-03-02 20:57:30 +01:00
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
UIxFilterFieldTypeAddress,
|
|
|
|
UIxFilterFieldTypeHeader,
|
2014-01-27 21:09:22 +01:00
|
|
|
UIxFilterFieldTypeBody,
|
2010-03-02 20:57:30 +01:00
|
|
|
UIxFilterFieldTypeSize,
|
|
|
|
} UIxFilterFieldType;
|
|
|
|
|
|
|
|
static NSArray *sieveOperators = nil;
|
|
|
|
static NSArray *sieveSizeOperators = nil;
|
|
|
|
static NSMutableDictionary *fieldTypes = nil;
|
|
|
|
static NSDictionary *sieveFields = nil;
|
|
|
|
static NSDictionary *sieveFlags = nil;
|
2014-01-27 21:09:22 +01:00
|
|
|
static NSDictionary *typeRequirements = nil;
|
2010-03-02 20:57:30 +01:00
|
|
|
static NSDictionary *operatorRequirements = nil;
|
2012-11-30 16:11:05 +01:00
|
|
|
static NSMutableDictionary *methodRequirements = nil;
|
2011-06-29 19:43:10 +02:00
|
|
|
static NSString *sieveScriptName = @"sogo";
|
|
|
|
|
2010-03-02 20:57:30 +01:00
|
|
|
|
|
|
|
@interface NSString (SOGoSieveExtension)
|
|
|
|
|
|
|
|
- (NSString *) asSieveQuotedString;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation NSString (SOGoSieveExtension)
|
|
|
|
|
|
|
|
- (NSString *) _asSingleLineSieveQuotedString
|
|
|
|
{
|
|
|
|
NSString *escapedString;
|
|
|
|
|
|
|
|
escapedString = [[self stringByReplacingString: @"\\"
|
|
|
|
withString: @"\\\\"]
|
|
|
|
stringByReplacingString: @"\""
|
|
|
|
withString: @"\\\""];
|
|
|
|
|
|
|
|
return [NSString stringWithFormat: @"\"%@\"", escapedString];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) _asMultiLineSieveQuotedString
|
|
|
|
{
|
|
|
|
NSArray *lines;
|
|
|
|
NSMutableArray *newLines;
|
|
|
|
NSString *line, *newText;
|
|
|
|
int count, max;
|
|
|
|
|
|
|
|
lines = [self componentsSeparatedByString: @"\n"];
|
|
|
|
max = [lines count];
|
|
|
|
newLines = [NSMutableArray arrayWithCapacity: max];
|
|
|
|
for (count = 0; count < max; count++)
|
|
|
|
{
|
|
|
|
line = [lines objectAtIndex: count];
|
|
|
|
if ([line length] > 0 && [line characterAtIndex: 0] == '.')
|
|
|
|
[newLines addObject: [NSString stringWithFormat: @".%@", line]];
|
|
|
|
else
|
|
|
|
[newLines addObject: line];
|
|
|
|
}
|
|
|
|
|
2011-07-05 18:54:39 +02:00
|
|
|
newText = [NSString stringWithFormat: @"text:\r\n%@\r\n.\r\n",
|
2010-03-02 20:57:30 +01:00
|
|
|
[newLines componentsJoinedByString: @"\n"]];
|
|
|
|
|
|
|
|
return newText;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) asSieveQuotedString
|
|
|
|
{
|
|
|
|
NSRange nlRange;
|
|
|
|
|
|
|
|
nlRange = [self rangeOfString: @"\n"];
|
|
|
|
|
|
|
|
return ((nlRange.length > 0)
|
|
|
|
? [self _asMultiLineSieveQuotedString]
|
|
|
|
: [self _asSingleLineSieveQuotedString]);
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2011-06-29 19:43:10 +02:00
|
|
|
@implementation SOGoSieveManager
|
2010-03-02 20:57:30 +01:00
|
|
|
|
|
|
|
+ (void) initialize
|
|
|
|
{
|
|
|
|
NSArray *fields;
|
|
|
|
|
|
|
|
if (!sieveOperators)
|
|
|
|
{
|
|
|
|
sieveOperators = [NSArray arrayWithObjects: @"is", @"contains",
|
|
|
|
@"matches", @"regex",
|
|
|
|
@"over", @"under", nil];
|
|
|
|
[sieveOperators retain];
|
|
|
|
}
|
|
|
|
if (!sieveSizeOperators)
|
|
|
|
{
|
|
|
|
sieveSizeOperators = [NSArray arrayWithObjects: @"over", @"under", nil];
|
|
|
|
[sieveSizeOperators retain];
|
|
|
|
}
|
|
|
|
if (!fieldTypes)
|
|
|
|
{
|
|
|
|
fieldTypes = [NSMutableDictionary new];
|
|
|
|
fields = [NSArray arrayWithObjects: @"to", @"cc", @"to_or_cc", @"from",
|
|
|
|
nil];
|
2014-01-27 21:09:22 +01:00
|
|
|
[fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeAddress]
|
2010-03-02 20:57:30 +01:00
|
|
|
forKeys: fields];
|
|
|
|
fields = [NSArray arrayWithObjects: @"header", @"subject", nil];
|
2014-01-27 21:09:22 +01:00
|
|
|
[fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeHeader]
|
2010-03-02 20:57:30 +01:00
|
|
|
forKeys: fields];
|
2014-01-27 21:09:22 +01:00
|
|
|
[fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeBody]
|
|
|
|
forKey: @"body"];
|
2010-03-02 20:57:30 +01:00
|
|
|
[fieldTypes setObject: [NSNumber numberWithInt: UIxFilterFieldTypeSize]
|
|
|
|
forKey: @"size"];
|
|
|
|
}
|
|
|
|
if (!sieveFields)
|
|
|
|
{
|
|
|
|
sieveFields
|
|
|
|
= [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
@"\"to\"", @"to",
|
|
|
|
@"\"cc\"", @"cc",
|
|
|
|
@"[\"to\", \"cc\"]", @"to_or_cc",
|
|
|
|
@"\"from\"", @"from",
|
|
|
|
@"\"subject\"", @"subject",
|
|
|
|
nil];
|
|
|
|
[sieveFields retain];
|
|
|
|
}
|
|
|
|
if (!sieveFlags)
|
|
|
|
{
|
|
|
|
sieveFlags
|
|
|
|
= [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
@"\\Answered", @"answered",
|
|
|
|
@"\\Deleted", @"deleted",
|
|
|
|
@"\\Draft", @"draft",
|
|
|
|
@"\\Flagged", @"flagged",
|
|
|
|
@"Junk", @"junk",
|
|
|
|
@"NotJunk", @"not_junk",
|
|
|
|
@"\\Seen", @"seen",
|
|
|
|
nil];
|
|
|
|
[sieveFlags retain];
|
|
|
|
}
|
2014-01-27 21:09:22 +01:00
|
|
|
if (!typeRequirements)
|
|
|
|
{
|
|
|
|
typeRequirements
|
|
|
|
= [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
@"body", [NSNumber numberWithInt: UIxFilterFieldTypeBody],
|
|
|
|
nil];
|
|
|
|
[typeRequirements retain];
|
|
|
|
}
|
2010-03-02 20:57:30 +01:00
|
|
|
if (!operatorRequirements)
|
|
|
|
{
|
|
|
|
operatorRequirements
|
|
|
|
= [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
@"regex", @"regex",
|
|
|
|
nil];
|
|
|
|
[operatorRequirements retain];
|
|
|
|
}
|
|
|
|
if (!methodRequirements)
|
|
|
|
{
|
|
|
|
methodRequirements
|
2012-11-30 16:11:05 +01:00
|
|
|
= [NSMutableDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
@"imapflags", @"addflag",
|
|
|
|
@"imapflags", @"removeflag",
|
|
|
|
@"imapflags", @"flag",
|
|
|
|
@"vacation", @"vacation",
|
|
|
|
@"notify", @"notify",
|
|
|
|
@"fileinto", @"fileinto",
|
|
|
|
@"reject", @"reject",
|
|
|
|
@"regex", @"regex",
|
|
|
|
nil];
|
2010-03-02 20:57:30 +01:00
|
|
|
[methodRequirements retain];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-29 19:43:10 +02:00
|
|
|
+ (id) sieveManagerForUser: (SOGoUser *) newUser
|
2010-03-02 20:57:30 +01:00
|
|
|
{
|
2011-06-29 19:43:10 +02:00
|
|
|
SOGoSieveManager *newManager;
|
2010-03-02 20:57:30 +01:00
|
|
|
|
2011-06-29 19:43:10 +02:00
|
|
|
newManager = [[self alloc] initForUser: newUser];
|
|
|
|
[newManager autorelease];
|
2010-03-02 20:57:30 +01:00
|
|
|
|
2011-06-29 19:43:10 +02:00
|
|
|
return newManager;
|
2010-03-02 20:57:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
- (id) init
|
|
|
|
{
|
|
|
|
if ((self = [super init]))
|
|
|
|
{
|
|
|
|
user = nil;
|
|
|
|
requirements = nil;
|
|
|
|
scriptError = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id) initForUser: (SOGoUser *) newUser
|
|
|
|
{
|
|
|
|
if ((self = [self init]))
|
|
|
|
{
|
|
|
|
ASSIGN (user, newUser);
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void) dealloc
|
|
|
|
{
|
|
|
|
[user release];
|
|
|
|
[requirements release];
|
|
|
|
[scriptError release];
|
|
|
|
[super dealloc];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL) _saveFilters
|
|
|
|
{
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL) _extractRuleField: (NSString **) field
|
|
|
|
fromRule: (NSDictionary *) rule
|
|
|
|
andType: (UIxFilterFieldType *) type
|
|
|
|
{
|
|
|
|
NSNumber *fieldType;
|
2014-01-27 21:09:22 +01:00
|
|
|
NSString *jsonField, *customHeader, *requirement;
|
2010-03-02 20:57:30 +01:00
|
|
|
|
|
|
|
jsonField = [rule objectForKey: @"field"];
|
|
|
|
if (jsonField)
|
|
|
|
{
|
|
|
|
fieldType = [fieldTypes objectForKey: jsonField];
|
|
|
|
if (fieldType)
|
|
|
|
{
|
|
|
|
*type = [fieldType intValue];
|
|
|
|
if ([jsonField isEqualToString: @"header"])
|
|
|
|
{
|
|
|
|
customHeader = [rule objectForKey: @"custom_header"];
|
|
|
|
if ([customHeader length])
|
|
|
|
*field = [customHeader asSieveQuotedString];
|
|
|
|
else
|
|
|
|
scriptError = (@"Pseudo-header field 'header' without"
|
|
|
|
@" 'custom_header' parameter.");
|
|
|
|
}
|
2014-01-27 21:09:22 +01:00
|
|
|
else if ([jsonField isEqualToString: @"body"] ||
|
|
|
|
[jsonField isEqualToString: @"size"])
|
2010-03-02 20:57:30 +01:00
|
|
|
*field = nil;
|
|
|
|
else
|
|
|
|
*field = [sieveFields objectForKey: jsonField];
|
2014-01-27 21:09:22 +01:00
|
|
|
|
|
|
|
requirement = [typeRequirements objectForKey: fieldType];
|
|
|
|
if (requirement)
|
|
|
|
[requirements addObjectUniquely: requirement];
|
2010-03-02 20:57:30 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
scriptError
|
|
|
|
= [NSString stringWithFormat: @"Rule based on unknown field '%@'",
|
2013-02-08 15:42:31 +01:00
|
|
|
jsonField];
|
2010-03-02 20:57:30 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
scriptError = @"Rule without any specified field.";
|
|
|
|
|
|
|
|
return (scriptError == nil);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL) _extractRuleOperator: (NSString **) operator
|
|
|
|
fromRule: (NSDictionary *) rule
|
|
|
|
isNot: (BOOL *) isNot
|
|
|
|
{
|
|
|
|
NSString *jsonOperator, *baseOperator, *requirement;
|
|
|
|
int baseLength;
|
|
|
|
|
|
|
|
jsonOperator = [rule objectForKey: @"operator"];
|
|
|
|
if (jsonOperator)
|
|
|
|
{
|
|
|
|
*isNot = [jsonOperator hasSuffix: @"_not"];
|
|
|
|
if (*isNot)
|
|
|
|
{
|
|
|
|
baseLength = [jsonOperator length] - 4;
|
|
|
|
baseOperator
|
|
|
|
= [jsonOperator substringWithRange: NSMakeRange (0, baseLength)];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
baseOperator = jsonOperator;
|
|
|
|
|
|
|
|
if ([sieveOperators containsObject: baseOperator])
|
|
|
|
{
|
|
|
|
requirement = [operatorRequirements objectForKey: baseOperator];
|
|
|
|
if (requirement)
|
|
|
|
[requirements addObjectUniquely: requirement];
|
|
|
|
*operator = baseOperator;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
scriptError = [NSString stringWithFormat:
|
|
|
|
@"Rule has unknown operator '%@'",
|
|
|
|
baseOperator];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
scriptError = @"Rule without any specified operator";
|
|
|
|
|
|
|
|
return (scriptError == nil);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL) _validateRuleOperator: (NSString *) operator
|
|
|
|
withFieldType: (UIxFilterFieldType) type
|
|
|
|
{
|
|
|
|
BOOL rc;
|
|
|
|
|
|
|
|
if (type == UIxFilterFieldTypeSize)
|
|
|
|
rc = [sieveSizeOperators containsObject: operator];
|
|
|
|
else
|
2014-01-27 21:09:22 +01:00
|
|
|
// Header and Body types
|
2010-03-02 20:57:30 +01:00
|
|
|
rc = (![sieveSizeOperators containsObject: operator]
|
|
|
|
&& [sieveOperators containsObject: operator]);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL) _extractRuleValue: (NSString **) value
|
|
|
|
fromRule: (NSDictionary *) rule
|
|
|
|
withFieldType: (UIxFilterFieldType) type
|
|
|
|
{
|
|
|
|
NSString *extractedValue;
|
|
|
|
|
|
|
|
extractedValue = [rule objectForKey: @"value"];
|
|
|
|
if (extractedValue)
|
|
|
|
{
|
|
|
|
if (type == UIxFilterFieldTypeSize)
|
|
|
|
*value = [NSString stringWithFormat: @"%d",
|
|
|
|
[extractedValue intValue]];
|
|
|
|
else
|
|
|
|
*value = [extractedValue asSieveQuotedString];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
scriptError = @"Rule lacks a 'value' parameter";
|
|
|
|
|
|
|
|
return (scriptError == nil);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) _composeSieveRuleOnField: (NSString *) field
|
|
|
|
withType: (UIxFilterFieldType) type
|
|
|
|
operator: (NSString *) operator
|
|
|
|
revert: (BOOL) revert
|
|
|
|
andValue: (NSString *) value
|
|
|
|
{
|
|
|
|
NSMutableString *sieveRule;
|
|
|
|
|
|
|
|
sieveRule = [NSMutableString stringWithCapacity: 100];
|
|
|
|
if (revert)
|
|
|
|
[sieveRule appendString: @"not "];
|
2014-01-27 21:09:22 +01:00
|
|
|
|
2010-03-02 20:57:30 +01:00
|
|
|
if (type == UIxFilterFieldTypeAddress)
|
|
|
|
[sieveRule appendString: @"address "];
|
|
|
|
else if (type == UIxFilterFieldTypeHeader)
|
|
|
|
[sieveRule appendString: @"header "];
|
2014-01-27 21:09:22 +01:00
|
|
|
else if (type == UIxFilterFieldTypeBody)
|
|
|
|
[sieveRule appendString: @"body :text "];
|
2010-03-02 20:57:30 +01:00
|
|
|
else if (type == UIxFilterFieldTypeSize)
|
|
|
|
[sieveRule appendString: @"size "];
|
|
|
|
[sieveRule appendFormat: @":%@ ", operator];
|
2014-01-27 21:09:22 +01:00
|
|
|
|
2010-03-02 20:57:30 +01:00
|
|
|
if (type == UIxFilterFieldTypeSize)
|
|
|
|
[sieveRule appendFormat: @"%@K", value];
|
2014-01-27 21:09:22 +01:00
|
|
|
else if (field)
|
2010-03-02 20:57:30 +01:00
|
|
|
[sieveRule appendFormat: @"%@ %@", field, value];
|
2014-01-27 21:09:22 +01:00
|
|
|
else
|
|
|
|
[sieveRule appendFormat: @"%@", value];
|
2010-03-02 20:57:30 +01:00
|
|
|
|
|
|
|
return sieveRule;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) _extractSieveRule: (NSDictionary *) rule
|
|
|
|
{
|
|
|
|
NSString *field, *operator, *value;
|
|
|
|
UIxFilterFieldType type;
|
|
|
|
BOOL isNot;
|
|
|
|
|
|
|
|
return (([self _extractRuleField: &field fromRule: rule andType: &type]
|
|
|
|
&& [self _extractRuleOperator: &operator fromRule: rule
|
|
|
|
isNot: &isNot]
|
|
|
|
&& [self _validateRuleOperator: operator
|
|
|
|
withFieldType: type]
|
|
|
|
&& [self _extractRuleValue: &value fromRule: rule
|
|
|
|
withFieldType: type])
|
|
|
|
? [self _composeSieveRuleOnField: field
|
|
|
|
withType: type
|
|
|
|
operator: operator
|
|
|
|
revert: isNot
|
|
|
|
andValue: value]
|
|
|
|
: nil);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *) _extractSieveRules: (NSArray *) rules
|
|
|
|
{
|
|
|
|
NSMutableArray *sieveRules;
|
|
|
|
NSString *sieveRule;
|
|
|
|
int count, max;
|
|
|
|
|
|
|
|
max = [rules count];
|
|
|
|
if (max)
|
|
|
|
{
|
|
|
|
sieveRules = [NSMutableArray arrayWithCapacity: max];
|
|
|
|
for (count = 0; !scriptError && count < max; count++)
|
|
|
|
{
|
|
|
|
sieveRule = [self _extractSieveRule: [rules objectAtIndex: count]];
|
|
|
|
if (sieveRule)
|
|
|
|
[sieveRules addObject: sieveRule];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
sieveRules = nil;
|
|
|
|
|
|
|
|
return sieveRules;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) _extractSieveAction: (NSDictionary *) action
|
|
|
|
{
|
2014-02-15 03:31:32 +01:00
|
|
|
NSString *sieveAction, *method, *requirement, *argument, *flag, *mailbox;
|
|
|
|
NSDictionary *mailLabels;
|
2010-03-02 20:57:30 +01:00
|
|
|
SOGoDomainDefaults *dd;
|
|
|
|
|
2010-04-21 16:35:58 +02:00
|
|
|
sieveAction = nil;
|
|
|
|
|
2010-03-02 20:57:30 +01:00
|
|
|
method = [action objectForKey: @"method"];
|
|
|
|
if (method)
|
|
|
|
{
|
|
|
|
argument = [action objectForKey: @"argument"];
|
|
|
|
if ([method isEqualToString: @"discard"]
|
|
|
|
|| [method isEqualToString: @"keep"]
|
|
|
|
|| [method isEqualToString: @"stop"])
|
|
|
|
sieveAction = method;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (argument)
|
|
|
|
{
|
|
|
|
if ([method isEqualToString: @"addflag"])
|
|
|
|
{
|
|
|
|
flag = [sieveFlags objectForKey: argument];
|
2014-02-15 03:31:32 +01:00
|
|
|
if (!flag)
|
|
|
|
{
|
|
|
|
mailLabels = [[user userDefaults] mailLabelsColors];
|
|
|
|
if ([mailLabels objectForKey: argument])
|
|
|
|
flag = argument;
|
|
|
|
}
|
2010-03-02 20:57:30 +01:00
|
|
|
if (flag)
|
|
|
|
sieveAction = [NSString stringWithFormat: @"%@ %@",
|
|
|
|
method, [flag asSieveQuotedString]];
|
|
|
|
else
|
|
|
|
scriptError
|
|
|
|
= [NSString stringWithFormat:
|
|
|
|
@"Action with invalid flag argument '%@'",
|
|
|
|
argument];
|
|
|
|
}
|
|
|
|
else if ([method isEqualToString: @"fileinto"])
|
|
|
|
{
|
|
|
|
dd = [user domainDefaults];
|
|
|
|
mailbox
|
2011-02-03 19:49:48 +01:00
|
|
|
= [[argument componentsSeparatedByString: @"/"]
|
2010-03-02 20:57:30 +01:00
|
|
|
componentsJoinedByString: [dd imapFolderSeparator]];
|
|
|
|
sieveAction = [NSString stringWithFormat: @"%@ %@",
|
|
|
|
method, [mailbox asSieveQuotedString]];
|
|
|
|
}
|
|
|
|
else if ([method isEqualToString: @"redirect"])
|
|
|
|
sieveAction = [NSString stringWithFormat: @"%@ %@",
|
|
|
|
method, [argument asSieveQuotedString]];
|
|
|
|
else if ([method isEqualToString: @"reject"])
|
2013-02-05 21:09:20 +01:00
|
|
|
sieveAction = [NSString stringWithFormat: @"%@ %@",
|
|
|
|
method, [argument asSieveQuotedString]];
|
2010-03-02 20:57:30 +01:00
|
|
|
else
|
|
|
|
scriptError
|
|
|
|
= [NSString stringWithFormat: @"Action has unknown method '%@'",
|
|
|
|
method];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
scriptError = @"Action missing 'argument' parameter";
|
|
|
|
}
|
|
|
|
if (method)
|
|
|
|
{
|
|
|
|
requirement = [methodRequirements objectForKey: method];
|
|
|
|
if (requirement)
|
|
|
|
[requirements addObjectUniquely: requirement];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2010-04-21 16:35:58 +02:00
|
|
|
scriptError = @"Action missing 'method' parameter";
|
2010-03-02 20:57:30 +01:00
|
|
|
|
|
|
|
return sieveAction;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray *) _extractSieveActions: (NSArray *) actions
|
|
|
|
{
|
|
|
|
NSMutableArray *sieveActions;
|
|
|
|
NSString *sieveAction;
|
|
|
|
int count, max;
|
|
|
|
|
|
|
|
max = [actions count];
|
|
|
|
sieveActions = [NSMutableArray arrayWithCapacity: max];
|
|
|
|
for (count = 0; !scriptError && count < max; count++)
|
|
|
|
{
|
|
|
|
sieveAction = [self _extractSieveAction: [actions objectAtIndex: count]];
|
|
|
|
if (!scriptError)
|
|
|
|
[sieveActions addObject: sieveAction];
|
|
|
|
}
|
|
|
|
|
|
|
|
return sieveActions;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) _convertScriptToSieve: (NSDictionary *) newScript
|
|
|
|
{
|
|
|
|
NSMutableString *sieveText;
|
|
|
|
NSString *match;
|
|
|
|
NSArray *sieveRules, *sieveActions;
|
|
|
|
|
|
|
|
sieveText = [NSMutableString stringWithCapacity: 1024];
|
|
|
|
match = [newScript objectForKey: @"match"];
|
|
|
|
if ([match isEqualToString: @"allmessages"])
|
|
|
|
match = nil;
|
|
|
|
if (match)
|
|
|
|
{
|
|
|
|
if ([match isEqualToString: @"all"] || [match isEqualToString: @"any"])
|
|
|
|
{
|
2014-01-27 21:09:22 +01:00
|
|
|
sieveRules = [self _extractSieveRules: [newScript objectForKey: @"rules"]];
|
2010-03-02 20:57:30 +01:00
|
|
|
if (sieveRules)
|
|
|
|
[sieveText appendFormat: @"if %@of (%@) {\r\n",
|
2014-01-27 21:09:22 +01:00
|
|
|
match,
|
|
|
|
[sieveRules componentsJoinedByString: @", "]];
|
2010-03-02 20:57:30 +01:00
|
|
|
else
|
|
|
|
scriptError = [NSString stringWithFormat:
|
2014-01-27 21:09:22 +01:00
|
|
|
@"Test '%@' used without any"
|
2010-03-02 20:57:30 +01:00
|
|
|
@" specified rule",
|
|
|
|
match];
|
|
|
|
}
|
|
|
|
else
|
2014-01-27 21:09:22 +01:00
|
|
|
scriptError = [NSString stringWithFormat: @"Bad test: %@", match];
|
2010-03-02 20:57:30 +01:00
|
|
|
}
|
2014-01-27 21:09:22 +01:00
|
|
|
sieveActions = [self _extractSieveActions: [newScript objectForKey: @"actions"]];
|
2010-03-02 20:57:30 +01:00
|
|
|
if ([sieveActions count])
|
|
|
|
[sieveText appendFormat: @" %@;\r\n",
|
|
|
|
[sieveActions componentsJoinedByString: @";\r\n "]];
|
|
|
|
|
|
|
|
if (match)
|
|
|
|
[sieveText appendFormat: @"}\r\n"];
|
|
|
|
|
|
|
|
return sieveText;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) sieveScriptWithRequirements: (NSMutableArray *) newRequirements
|
|
|
|
{
|
|
|
|
NSMutableString *sieveScript;
|
|
|
|
NSString *sieveText;
|
|
|
|
NSArray *scripts;
|
|
|
|
int count, max;
|
|
|
|
NSDictionary *currentScript;
|
|
|
|
|
|
|
|
sieveScript = [NSMutableString stringWithCapacity: 8192];
|
|
|
|
|
|
|
|
ASSIGN (requirements, newRequirements);
|
|
|
|
[scriptError release];
|
|
|
|
scriptError = nil;
|
|
|
|
|
|
|
|
scripts = [[user userDefaults] sieveFilters];
|
|
|
|
max = [scripts count];
|
|
|
|
if (max)
|
|
|
|
{
|
|
|
|
for (count = 0; !scriptError && count < max; count++)
|
|
|
|
{
|
|
|
|
currentScript = [scripts objectAtIndex: count];
|
|
|
|
if ([[currentScript objectForKey: @"active"] boolValue])
|
|
|
|
{
|
|
|
|
sieveText = [self _convertScriptToSieve: currentScript];
|
|
|
|
[sieveScript appendString: sieveText];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[scriptError retain];
|
|
|
|
[requirements release];
|
|
|
|
requirements = nil;
|
|
|
|
|
|
|
|
if (scriptError)
|
|
|
|
sieveScript = nil;
|
|
|
|
|
|
|
|
return sieveScript;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *) lastScriptError
|
|
|
|
{
|
|
|
|
return scriptError;
|
|
|
|
}
|
|
|
|
|
2011-06-29 19:43:10 +02:00
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
2014-01-27 21:09:22 +01:00
|
|
|
- (NGSieveClient *) clientForAccount: (SOGoMailAccount *) theAccount
|
2011-06-29 19:43:10 +02:00
|
|
|
{
|
2014-01-27 21:09:22 +01:00
|
|
|
NSDictionary *result;
|
|
|
|
NSString *login, *authname, *password;
|
2011-06-29 19:43:10 +02:00
|
|
|
SOGoDomainDefaults *dd;
|
|
|
|
NGSieveClient *client;
|
2014-01-27 21:09:22 +01:00
|
|
|
NSString *sieveServer, *sieveScheme, *sieveQuery, *imapServer;
|
2012-12-03 22:49:20 +01:00
|
|
|
NSURL *url, *cUrl;
|
2011-06-29 19:43:10 +02:00
|
|
|
int sievePort;
|
2014-01-27 21:09:22 +01:00
|
|
|
BOOL connected;
|
2011-06-29 19:43:10 +02:00
|
|
|
|
|
|
|
dd = [user domainDefaults];
|
2011-11-17 20:16:46 +01:00
|
|
|
connected = YES;
|
2011-06-29 19:43:10 +02:00
|
|
|
|
2014-01-27 21:09:22 +01:00
|
|
|
// Extract credentials from mail account
|
|
|
|
login = [[theAccount imap4URL] user];
|
|
|
|
authname = [[theAccount imap4URL] user];
|
|
|
|
password = [theAccount imap4PasswordRenewed: NO];
|
|
|
|
|
2012-11-30 16:11:05 +01:00
|
|
|
// We connect to our Sieve server and check capabilities, in order
|
|
|
|
// to generate the right script, based on capabilities
|
|
|
|
//
|
|
|
|
// sieveServer might have the following format:
|
|
|
|
//
|
|
|
|
// sieve://localhost
|
|
|
|
// sieve://localhost:2000
|
|
|
|
// sieve://localhost:2000/?tls=YES
|
|
|
|
//
|
|
|
|
// Values such as "localhost" or "localhost:2000" are NOT supported.
|
|
|
|
//
|
|
|
|
// We first try to get the user's preferred Sieve server
|
|
|
|
sieveServer = [[[user mailAccounts] objectAtIndex: 0] objectForKey: @"sieveServerName"];
|
2012-12-03 22:49:20 +01:00
|
|
|
imapServer = [[[user mailAccounts] objectAtIndex: 0] objectForKey: @"serverName"];
|
2012-11-30 16:11:05 +01:00
|
|
|
|
2012-12-05 21:52:50 +01:00
|
|
|
cUrl = [NSURL URLWithString: (sieveServer ? sieveServer : @"")];
|
|
|
|
|
2012-12-06 15:03:17 +01:00
|
|
|
if ([dd sieveServer] && [[dd sieveServer] length] > 0)
|
2012-12-05 21:52:50 +01:00
|
|
|
url = [NSURL URLWithString: [dd sieveServer]];
|
|
|
|
else
|
|
|
|
url = [NSURL URLWithString: @"localhost"];
|
2012-12-03 22:49:20 +01:00
|
|
|
|
|
|
|
if ([cUrl host])
|
|
|
|
sieveServer = [cUrl host];
|
|
|
|
if (!sieveServer && [url host])
|
|
|
|
sieveServer = [url host];
|
|
|
|
if (!sieveServer && [dd sieveServer])
|
2012-11-30 16:11:05 +01:00
|
|
|
sieveServer = [dd sieveServer];
|
2012-12-03 22:49:20 +01:00
|
|
|
if (!sieveServer && imapServer)
|
2012-12-05 21:52:50 +01:00
|
|
|
sieveServer = [[NSURL URLWithString: imapServer] host];
|
2012-11-30 16:11:05 +01:00
|
|
|
if (!sieveServer)
|
2012-12-03 22:49:20 +01:00
|
|
|
sieveServer = @"localhost";
|
|
|
|
|
|
|
|
sieveScheme = [cUrl scheme] ? [cUrl scheme] : [url scheme];
|
|
|
|
if (!sieveScheme)
|
|
|
|
sieveScheme = @"sieve";
|
|
|
|
|
|
|
|
if ([cUrl port])
|
|
|
|
sievePort = [[cUrl port] intValue];
|
2012-11-30 16:11:05 +01:00
|
|
|
else
|
2012-12-03 22:49:20 +01:00
|
|
|
if ([url port])
|
|
|
|
sievePort = [[url port] intValue];
|
|
|
|
else
|
|
|
|
sievePort = 2000;
|
|
|
|
|
|
|
|
sieveQuery = [cUrl query] ? [cUrl query] : [url query];
|
|
|
|
if (sieveQuery)
|
|
|
|
sieveQuery = [NSString stringWithFormat: @"/?%@", sieveQuery];
|
|
|
|
else
|
|
|
|
sieveQuery = @"";
|
|
|
|
|
|
|
|
url = [NSURL URLWithString: [NSString stringWithFormat: @"%@://%@:%d%@",
|
|
|
|
sieveScheme, sieveServer, sievePort, sieveQuery]];
|
2012-11-30 16:11:05 +01:00
|
|
|
|
|
|
|
client = [[NGSieveClient alloc] initWithURL: url];
|
2014-01-27 21:09:22 +01:00
|
|
|
|
2012-11-30 16:11:05 +01:00
|
|
|
if (!client) {
|
|
|
|
NSLog(@"Sieve connection failed on %@", [url description]);
|
2014-01-27 21:09:22 +01:00
|
|
|
return nil;
|
2012-11-30 16:11:05 +01:00
|
|
|
}
|
2014-01-27 21:09:22 +01:00
|
|
|
|
|
|
|
if (!password) {
|
2012-11-30 16:11:05 +01:00
|
|
|
[client closeConnection];
|
2014-01-27 21:09:22 +01:00
|
|
|
return nil;
|
2012-11-30 16:11:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
NS_DURING
|
|
|
|
{
|
2014-01-27 21:09:22 +01:00
|
|
|
result = [client login: login authname: authname password: password];
|
2012-11-30 16:11:05 +01:00
|
|
|
}
|
|
|
|
NS_HANDLER
|
|
|
|
{
|
|
|
|
connected = NO;
|
|
|
|
}
|
|
|
|
NS_ENDHANDLER
|
|
|
|
|
|
|
|
if (!connected)
|
|
|
|
{
|
|
|
|
NSLog(@"Sieve connection failed on %@", [url description]);
|
2014-01-27 21:09:22 +01:00
|
|
|
return nil;
|
2012-11-30 16:11:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (![[result valueForKey:@"result"] boolValue]) {
|
|
|
|
NSLog(@"failure. Attempting with a renewed password (no authname supported)");
|
2014-01-27 21:09:22 +01:00
|
|
|
password = [theAccount imap4PasswordRenewed: YES];
|
|
|
|
result = [client login: login password: password];
|
2012-11-30 16:11:05 +01:00
|
|
|
}
|
2014-01-27 21:09:22 +01:00
|
|
|
|
2012-11-30 16:11:05 +01:00
|
|
|
if (![[result valueForKey:@"result"] boolValue]) {
|
|
|
|
NSLog(@"Could not login '%@' on Sieve server: %@: %@",
|
2014-01-27 21:09:22 +01:00
|
|
|
login, client, result);
|
2012-11-30 16:11:05 +01:00
|
|
|
[client closeConnection];
|
2014-01-27 21:09:22 +01:00
|
|
|
return nil;
|
2012-11-30 16:11:05 +01:00
|
|
|
}
|
2014-01-27 21:09:22 +01:00
|
|
|
|
|
|
|
return client;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
//
|
|
|
|
//
|
|
|
|
- (BOOL) updateFiltersForAccount: (SOGoMailAccount *) theAccount
|
|
|
|
{
|
|
|
|
NSMutableArray *req;
|
|
|
|
NSMutableString *script, *header;
|
|
|
|
NSDictionary *result, *values;
|
|
|
|
SOGoUserDefaults *ud;
|
|
|
|
SOGoDomainDefaults *dd;
|
|
|
|
NGSieveClient *client;
|
|
|
|
NSString *filterScript, *v;
|
|
|
|
BOOL b;
|
|
|
|
|
|
|
|
dd = [user domainDefaults];
|
|
|
|
if (!([dd sieveScriptsEnabled] || [dd vacationEnabled] || [dd forwardEnabled]))
|
|
|
|
return YES;
|
|
|
|
|
|
|
|
req = [NSMutableArray arrayWithCapacity: 15];
|
|
|
|
ud = [user userDefaults];
|
|
|
|
|
|
|
|
client = [self clientForAccount: theAccount];
|
|
|
|
if (!client)
|
|
|
|
return NO;
|
|
|
|
|
2012-11-30 16:11:05 +01:00
|
|
|
// We adjust the "methodRequirements" based on the server's
|
|
|
|
// capabilities. Cyrus exposes "imapflags" while Dovecot (and
|
|
|
|
// potentially others) expose "imap4flags" as specified in RFC5332
|
|
|
|
if ([client hasCapability: @"imap4flags"])
|
|
|
|
{
|
|
|
|
[methodRequirements setObject: @"imap4flags" forKey: @"addflag"];
|
|
|
|
[methodRequirements setObject: @"imap4flags" forKey: @"removeflag"];
|
|
|
|
[methodRequirements setObject: @"imap4flags" forKey: @"flag"];
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Now let's generate the script
|
|
|
|
//
|
2011-06-29 19:43:10 +02:00
|
|
|
script = [NSMutableString string];
|
|
|
|
|
2012-07-25 17:31:40 +02:00
|
|
|
// We first handle filters
|
|
|
|
filterScript = [self sieveScriptWithRequirements: req];
|
|
|
|
if (filterScript)
|
|
|
|
{
|
|
|
|
if ([filterScript length])
|
|
|
|
{
|
|
|
|
b = YES;
|
|
|
|
[script appendString: filterScript];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
NSLog(@"Sieve generation failure: %@", [self lastScriptError]);
|
2012-11-30 16:11:05 +01:00
|
|
|
[client closeConnection];
|
2012-07-25 17:31:40 +02:00
|
|
|
return NO;
|
|
|
|
}
|
2011-06-29 19:43:10 +02:00
|
|
|
|
|
|
|
// We handle vacation messages.
|
|
|
|
// See http://ietfreport.isoc.org/idref/draft-ietf-sieve-vacation/
|
2012-07-25 17:31:40 +02:00
|
|
|
values = [ud vacationOptions];
|
|
|
|
|
2011-06-29 19:43:10 +02:00
|
|
|
if (values && [[values objectForKey: @"enabled"] boolValue])
|
|
|
|
{
|
|
|
|
NSArray *addresses;
|
|
|
|
NSString *text;
|
|
|
|
BOOL ignore;
|
|
|
|
int days, i;
|
|
|
|
|
|
|
|
days = [[values objectForKey: @"daysBetweenResponse"] intValue];
|
|
|
|
addresses = [values objectForKey: @"autoReplyEmailAddresses"];
|
|
|
|
ignore = [[values objectForKey: @"ignoreLists"] boolValue];
|
|
|
|
text = [values objectForKey: @"autoReplyText"];
|
|
|
|
b = YES;
|
|
|
|
|
|
|
|
if (days == 0)
|
2013-02-05 21:09:20 +01:00
|
|
|
days = 7;
|
2011-06-29 19:43:10 +02:00
|
|
|
|
|
|
|
[req addObjectUniquely: @"vacation"];
|
|
|
|
|
|
|
|
// Skip mailing lists
|
|
|
|
if (ignore)
|
2013-02-05 21:09:20 +01:00
|
|
|
[script appendString: @"if allof ( not exists [\"list-help\", \"list-unsubscribe\", \"list-subscribe\", \"list-owner\", \"list-post\", \"list-archive\", \"list-id\", \"Mailing-List\"], not header :comparator \"i;ascii-casemap\" :is \"Precedence\" [\"list\", \"bulk\", \"junk\"], not header :comparator \"i;ascii-casemap\" :matches \"To\" \"Multiple recipients of*\" ) {"];
|
2011-06-29 19:43:10 +02:00
|
|
|
|
|
|
|
[script appendFormat: @"vacation :days %d :addresses [", days];
|
|
|
|
|
|
|
|
for (i = 0; i < [addresses count]; i++)
|
2013-02-05 21:09:20 +01:00
|
|
|
{
|
|
|
|
[script appendFormat: @"\"%@\"", [addresses objectAtIndex: i]];
|
2011-06-29 19:43:10 +02:00
|
|
|
|
2013-02-05 21:09:20 +01:00
|
|
|
if (i == [addresses count]-1)
|
|
|
|
[script appendString: @"] "];
|
|
|
|
else
|
|
|
|
[script appendString: @", "];
|
|
|
|
}
|
2011-06-29 19:43:10 +02:00
|
|
|
|
|
|
|
[script appendFormat: @"text:\r\n%@\r\n.\r\n;\r\n", text];
|
|
|
|
|
|
|
|
if (ignore)
|
2013-02-05 21:09:20 +01:00
|
|
|
[script appendString: @"}\r\n"];
|
2011-06-29 19:43:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// We handle mail forward
|
|
|
|
values = [ud forwardOptions];
|
|
|
|
|
|
|
|
if (values && [[values objectForKey: @"enabled"] boolValue])
|
|
|
|
{
|
|
|
|
id addresses;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
b = YES;
|
|
|
|
|
|
|
|
addresses = [values objectForKey: @"forwardAddress"];
|
|
|
|
if ([addresses isKindOfClass: [NSString class]])
|
|
|
|
addresses = [NSArray arrayWithObject: addresses];
|
|
|
|
|
|
|
|
for (i = 0; i < [addresses count]; i++)
|
2013-02-05 21:09:20 +01:00
|
|
|
{
|
2011-06-29 19:43:10 +02:00
|
|
|
v = [addresses objectAtIndex: i];
|
|
|
|
if (v && [v length] > 0)
|
|
|
|
[script appendFormat: @"redirect \"%@\";\r\n", v];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([[values objectForKey: @"keepCopy"] boolValue])
|
2013-02-05 21:09:20 +01:00
|
|
|
[script appendString: @"keep;\r\n"];
|
2011-06-29 19:43:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ([req count])
|
|
|
|
{
|
|
|
|
header = [NSString stringWithFormat: @"require [\"%@\"];\r\n",
|
|
|
|
[req componentsJoinedByString: @"\",\""]];
|
|
|
|
[script insertString: header atIndex: 0];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* We ensure to deactive the current active script since it could prevent
|
|
|
|
its deletion from the server. */
|
|
|
|
result = [client setActiveScript: @""];
|
|
|
|
// We delete the existing Sieve script
|
|
|
|
result = [client deleteScript: sieveScriptName];
|
|
|
|
|
|
|
|
if (![[result valueForKey:@"result"] boolValue]) {
|
|
|
|
NSLog(@"WARNING: Could not delete Sieve script - continuing...: %@", result);
|
|
|
|
}
|
|
|
|
|
|
|
|
// We put and activate the script only if we actually have a script
|
|
|
|
// that does something...
|
|
|
|
if (b)
|
|
|
|
{
|
|
|
|
result = [client putScript: sieveScriptName script: script];
|
|
|
|
|
|
|
|
if (![[result valueForKey:@"result"] boolValue]) {
|
2013-02-05 21:09:20 +01:00
|
|
|
NSLog(@"Could not upload Sieve script: %@", result);
|
|
|
|
[client closeConnection];
|
|
|
|
return NO;
|
2011-06-29 19:43:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
result = [client setActiveScript: sieveScriptName];
|
|
|
|
if (![[result valueForKey:@"result"] boolValue]) {
|
2013-02-05 21:09:20 +01:00
|
|
|
NSLog(@"Could not enable Sieve script: %@", result);
|
|
|
|
[client closeConnection];
|
|
|
|
return NO;
|
2011-06-29 19:43:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-05 21:09:55 +01:00
|
|
|
[client closeConnection];
|
2011-06-29 19:43:10 +02:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2010-03-02 20:57:30 +01:00
|
|
|
@end
|