2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
24 #include "NGSieveClient.h"
25 #include "NGImap4Support.h"
26 #include "NGImap4ResponseParser.h"
27 #include "NSString+Imap4.h"
31 @interface NGSieveClient(Private)
33 - (NGHashMap *)processCommand:(NSString *)_command;
34 - (NGHashMap *)processCommand:(NSString *)_command logText:(NSString *)_txt;
36 - (void)sendCommand:(NSString *)_command;
37 - (void)sendCommand:(NSString *)_command logText:(NSString *)_txt;
39 - (NSMutableDictionary *)normalizeResponse:(NGHashMap *)_map;
40 - (NSMutableDictionary *)normalizeOpenConnectionResponse:(NGHashMap *)_map;
41 - (NSDictionary *)login;
46 ** An implementation of an Imap4 client
48 ** A folder name always looks like an absolute filename (/inbox/doof)
52 @implementation NGSieveClient
54 static int defaultSievePort = 143;
55 static NSNumber *YesNumber = nil;
56 static NSNumber *NoNumber = nil;
57 static BOOL ProfileImapEnabled = NO;
58 static BOOL LOG_PASSWORD = NO;
59 static BOOL debugImap4 = NO;
62 static BOOL didInit = NO;
67 ud = [NSUserDefaults standardUserDefaults];
68 LOG_PASSWORD = [ud boolForKey:@"SieveLogPassword"];
69 ProfileImapEnabled = [ud boolForKey:@"ProfileImapEnabled"];
70 debugImap4 = [ud boolForKey:@"ImapDebugEnabled"];
72 YesNumber = [[NSNumber numberWithBool:YES] retain];
73 NoNumber = [[NSNumber numberWithBool:NO] retain];
76 + (id)clientWithAddress:(id<NGSocketAddress>)_address {
78 [[(NGSieveClient *)[self alloc] initWithAddress:_address] autorelease];
81 + (id)clientWithHost:(id)_host {
82 return [[[self alloc] initWithHost:_host] autorelease];
85 - (id)initWithHost:(id)_host {
86 NGInternetSocketAddress *a;
87 a = [NGInternetSocketAddress addressWithPort:defaultSievePort onHost:_host];
88 return [self initWithAddress:a];
92 ** designated initializer
95 - (id)initWithAddress:(id<NGSocketAddress>)_address {
96 if ((self = [super init])) {
97 self->address = [_address retain];
98 self->debug = debugImap4;
104 [self->text release];
105 [self->address release];
106 [self->socket release];
107 [self->parser release];
108 [self->login release];
109 [self->password release];
115 - (BOOL)isEqual:(id)_obj {
118 if ([_obj isKindOfClass:[NGSieveClient class]])
119 return [self isEqualToSieveClient:_obj];
123 - (BOOL)isEqualToSieveClient:(NGSieveClient *)_obj {
124 if (_obj == self) return YES;
125 if (_obj == nil) return NO;
126 return [[_obj address] isEqual:self->address];
131 - (id<NGActiveSocket>)socket {
135 - (id<NGSocketAddress>)address {
136 return self->address;
142 ** Opens a connection to given Host.
145 - (NSDictionary *)openConnection {
149 if (ProfileImapEnabled) {
150 gettimeofday(&tv, NULL);
151 ti = (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0);
154 [self->socket release]; self->socket = nil;
155 [self->parser release]; self->parser = nil;
156 [self->text release]; self->text = nil;
159 [[NGActiveSocket socketConnectedToAddress:self->address] retain];
161 [(NGCTextStream *)[NGCTextStream alloc] initWithSource:self->socket];
162 self->parser = [[NGImap4ResponseParser alloc] initWithStream:self->socket];
164 /* receive greeting from server without tag-id */
166 if (ProfileImapEnabled) {
167 gettimeofday(&tv, NULL);
168 ti = (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0) - ti;
169 fprintf(stderr, "[%s] <openConnection> : time needed: %4.4fs\n",
170 __PRETTY_FUNCTION__, ti < 0.0 ? -1.0 : ti);
172 return [self normalizeOpenConnectionResponse:
173 [self->parser parseSieveResponse]];
177 ** Check whether stream is already open (could be closed because server-timeout)
180 - (NSNumber *)isConnected {
181 return [NSNumber numberWithBool:
182 (self->socket == nil)
183 ? NO : [(NGActiveSocket *)self->socket isAlive]];
187 ** Close a consisting connection.
190 - (void)closeConnection {
191 [self->socket close];
192 [self->socket release]; self->socket = nil;
193 [self->text release]; self->text = nil;
194 [self->parser release]; self->parser = nil;
198 ** login with plaintext password authenticating
201 - (NSDictionary *)login:(NSString *)_login password:(NSString *)_passwd {
202 if ((_login == nil) || (_passwd == nil))
205 [self->login release]; self->login = nil;
206 [self->password release]; self->password = nil;
208 self->login = [_login copy];
209 self->password = [_passwd copy];
214 [self closeConnection];
215 [self openConnection];
219 - (NSDictionary *)login {
220 NGHashMap *map = nil;
226 logLen = [self->login cStringLength];
227 bufLen = (logLen*2) + [self->password cStringLength] +2;
229 buf = calloc(sizeof(char), bufLen);
231 sprintf(buf, "%s %s %s",
232 [self->login cString], [self->login cString],
233 [self->password cString]);
236 buf[logLen * 2 + 1] = '\0';
238 auth = [NSData dataWithBytesNoCopy:buf length:bufLen];
239 auth = [auth dataByEncodingBase64];
241 if (LOG_PASSWORD == 1) {
244 s = [NSString stringWithFormat:@"AUTHENTICATE \"PLAIN\" {%d+}\r\n%s",
245 [auth length], [auth bytes]];
246 map = [self processCommand:s];
251 s = [NSString stringWithFormat:@"AUTHENTICATE \"PLAIN\" {%d+}\r\n%s",
252 [auth length], [auth bytes]];
253 map = [self processCommand:s
254 logText:@"AUTHENTICATE \"PLAIN\" {%d+}\r\nLOGIN:PASSWORD\r\n"];
256 return [self normalizeResponse:map];
260 logout from the connected host and close the connection
263 - (NSDictionary *)logout {
266 map = [self processCommand:@"logout"]; // TODO: check for success!
267 [self closeConnection];
268 return [self normalizeResponse:map];
271 - (NSDictionary *)getScript:(NSString *)_scriptName {
273 [self notImplemented:_cmd];
277 - (BOOL)isValidScriptName:(NSString *)_name {
278 return ([_name length] == 0) ? NO : YES;
281 - (NSDictionary *)putScript:(NSString *)_name script:(NSString *)_script {
282 // TODO: script should be send in UTF-8!
286 if (![self isValidScriptName:_name]) {
287 [self logWithFormat:@"%s: missing script-name", __PRETTY_FUNCTION__];
290 if ([_script length] == 0) {
291 [self logWithFormat:@"%s: missing script", __PRETTY_FUNCTION__];
296 s = [s stringByAppendingString:_name];
297 s = [s stringByAppendingString:@"\" {"];
298 s = [s stringByAppendingFormat:@"{%d+}\r\n%@", [_script length], _script];
299 s = [s stringByAppendingString:@"\r\n"];
300 map = [self processCommand:s];
301 return [self normalizeResponse:map];
304 - (NSDictionary *)setActiveScript:(NSString *)_name {
307 if (![self isValidScriptName:_name]) {
308 NSLog(@"%s: missing script-name", __PRETTY_FUNCTION__);
311 map = [self processCommand:
312 [NSString stringWithFormat:@"SETACTIVE \"%@\"\r\n", _name]];
313 return [self normalizeResponse:map];
316 - (NSDictionary *)deleteScript:(NSString *)_name {
320 if (![self isValidScriptName:_name]) {
321 NSLog(@"%s: missing script-name", __PRETTY_FUNCTION__);
325 s = [NSString stringWithFormat:@"DELETESCRIPT \"%@\"\r\n", _name];
326 map = [self processCommand:s];
327 return [self normalizeResponse:map];
329 - (NSDictionary *)listScript:(NSString *)_script {
330 [self notImplemented:_cmd];
336 ** Filter for all responses
337 ** result : NSNumber (response result)
338 ** exists : NSNumber (number of exists mails in selectet folder
339 ** recent : NSNumber (number of recent mails in selectet folder
340 ** expunge : NSArray (message sequence number of expunged mails
344 - (NSMutableDictionary *)normalizeResponse:(NGHashMap *)_map {
345 id keys[3], values[3];
346 NSParameterAssert(_map != nil);
348 keys[0] = @"RawResponse"; values[0] = _map;
350 values[1] = [[_map objectForKey:@"ok"] boolValue] ? YesNumber : NoNumber;
351 return [NSMutableDictionary dictionaryWithObjects:values
352 forKeys:keys count:2];
356 ** filter for open connection
359 - (NSDictionary *)normalizeOpenConnectionResponse:(NGHashMap *)_map {
360 NSMutableDictionary *result;
363 result = [self normalizeResponse:_map];
365 if (![[[_map objectEnumeratorForKey:@"ok"] nextObject] boolValue])
368 if ((tmp = [_map objectForKey:@"implementation"]))
369 [result setObject:tmp forKey:@"server"];
370 if ((tmp = [_map objectForKey:@"sieve"]))
371 [result setObject:tmp forKey:@"capabilities"];
379 ** list : NSDictionary (folder name as key and flags as value)
382 - (NSString *)description {
383 return [NSString stringWithFormat:@"<Imap4Client[0x%08X]: socket=%@>",
384 (unsigned)self, [self socket]];
387 /* Private Methods */
389 - (BOOL)handleProcessException:(NSException *)_exception
390 repetitionCount:(int)_cnt
397 if ([_exception isKindOfClass:[NGIOException class]]) {
399 @"WARNING: got exception try to restore connection: %@",
403 if ([_exception isKindOfClass:[NGImap4ParserException class]]) {
405 @"WARNING: Got Parser-Exception try to restore connection: %@",
414 - (void)waitPriorReconnectWithRepetitionCount:(int)_cnt {
418 [self logWithFormat:@"reconnect to %@, sleeping %d seconds ...",
419 self->address, timeout];
421 [self logWithFormat:@"reconnect ..."];
424 - (NGHashMap *)processCommand:(NSString *)_command logText:(NSString *)_txt {
425 NGHashMap *map = nil;
426 BOOL repeatCommand = NO;
431 if (ProfileImapEnabled) {
432 gettimeofday(&tv, NULL);
433 ti = (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0);
434 fprintf(stderr, "{");
436 do { /* TODO: shouldn't that be a while loop? */
439 [self waitPriorReconnectWithRepetitionCount:repeatCnt];
447 [self sendCommand:_command logText:_txt];
448 map = [self->parser parseSieveResponse];
451 repeatCommand = [self handleProcessException:localException
452 repetitionCount:repeatCnt];
456 while (repeatCommand);
458 if (ProfileImapEnabled) {
459 gettimeofday(&tv, NULL);
460 ti = (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0) - ti;
461 fprintf(stderr, "}[%s] <Send Command> : time needed: %4.4fs\n",
462 __PRETTY_FUNCTION__, ti < 0.0 ? -1.0 : ti);
468 - (NGHashMap *)processCommand:(NSString *)_command {
469 return [self processCommand:_command logText:_command];
472 - (void)sendCommand:(NSString *)_command logText:(NSString *)_txt {
473 // TODO: should accept 'NSData' commands
474 NSString *command = nil;
479 fprintf(stderr, "C: %s\n", [_txt cString]);
481 [self->text writeString:command];
482 [self->text writeString:@"\r\n"];
486 - (void)sendCommand:(NSString *)_command {
487 [self sendCommand:_command logText:_command];
490 @end /* NGSieveClient */