2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OGo
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
25 #include "NGSieveClient.h"
26 #include "NGImap4Support.h"
27 #include "NGImap4ResponseParser.h"
28 #include "NSString+Imap4.h"
32 @interface NGSieveClient(Private)
34 - (NGHashMap *)processCommand:(NSString *)_command;
35 - (NGHashMap *)processCommand:(NSString *)_command logText:(NSString *)_txt;
37 - (void)sendCommand:(NSString *)_command;
38 - (void)sendCommand:(NSString *)_command logText:(NSString *)_txt;
40 - (NSMutableDictionary *)normalizeResponse:(NGHashMap *)_map;
41 - (NSMutableDictionary *)normalizeOpenConnectionResponse:(NGHashMap *)_map;
42 - (NSDictionary *)login;
47 ** An implementation of an Imap4 client
49 ** A folder name always looks like an absolute filename (/inbox/doof)
53 @implementation NGSieveClient
55 static int defaultSievePort = 143;
56 static NSNumber *YesNumber = nil;
57 static NSNumber *NoNumber = nil;
58 static BOOL ProfileImapEnabled = NO;
59 static BOOL LOG_PASSWORD = NO;
60 static BOOL debugImap4 = NO;
63 static BOOL didInit = NO;
68 ud = [NSUserDefaults standardUserDefaults];
69 LOG_PASSWORD = [ud boolForKey:@"SieveLogPassword"];
70 ProfileImapEnabled = [ud boolForKey:@"ProfileImapEnabled"];
71 debugImap4 = [ud boolForKey:@"ImapDebugEnabled"];
73 YesNumber = [[NSNumber numberWithBool:YES] retain];
74 NoNumber = [[NSNumber numberWithBool:NO] retain];
77 + (id)clientWithAddress:(id<NGSocketAddress>)_address {
79 [[(NGSieveClient *)[self alloc] initWithAddress:_address] autorelease];
82 + (id)clientWithHost:(id)_host {
83 return [[[self alloc] initWithHost:_host] autorelease];
86 - (id)initWithHost:(id)_host {
87 NGInternetSocketAddress *a;
88 a = [NGInternetSocketAddress addressWithPort:defaultSievePort onHost:_host];
89 return [self initWithAddress:a];
93 ** designated initializer
96 - (id)initWithAddress:(id<NGSocketAddress>)_address {
97 if ((self = [super init])) {
98 self->address = [_address retain];
99 self->debug = debugImap4;
105 [self->text release];
106 [self->address release];
107 [self->socket release];
108 [self->parser release];
109 [self->login release];
110 [self->password release];
116 - (BOOL)isEqual:(id)_obj {
119 if ([_obj isKindOfClass:[NGSieveClient class]])
120 return [self isEqualToSieveClient:_obj];
124 - (BOOL)isEqualToSieveClient:(NGSieveClient *)_obj {
125 if (_obj == self) return YES;
126 if (_obj == nil) return NO;
127 return [[_obj address] isEqual:self->address];
132 - (id<NGActiveSocket>)socket {
136 - (id<NGSocketAddress>)address {
137 return self->address;
143 ** Opens a connection to given Host.
146 - (NSDictionary *)openConnection {
150 if (ProfileImapEnabled) {
151 gettimeofday(&tv, NULL);
152 ti = (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0);
155 [self->socket release]; self->socket = nil;
156 [self->parser release]; self->parser = nil;
157 [self->text release]; self->text = nil;
160 [[NGActiveSocket socketConnectedToAddress:self->address] retain];
162 [(NGCTextStream *)[NGCTextStream alloc] initWithSource:self->socket];
163 self->parser = [[NGImap4ResponseParser alloc] initWithStream:self->socket];
165 /* receive greeting from server without tag-id */
167 if (ProfileImapEnabled) {
168 gettimeofday(&tv, NULL);
169 ti = (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0) - ti;
170 fprintf(stderr, "[%s] <openConnection> : time needed: %4.4fs\n",
171 __PRETTY_FUNCTION__, ti < 0.0 ? -1.0 : ti);
173 return [self normalizeOpenConnectionResponse:
174 [self->parser parseSieveResponse]];
178 ** Check whether stream is already open (could be closed because server-timeout)
181 - (NSNumber *)isConnected {
182 return [NSNumber numberWithBool:
183 (self->socket == nil)
184 ? NO : [(NGActiveSocket *)self->socket isAlive]];
188 ** Close a consisting connection.
191 - (void)closeConnection {
192 [self->socket close];
193 [self->socket release]; self->socket = nil;
194 [self->text release]; self->text = nil;
195 [self->parser release]; self->parser = nil;
199 ** login with plaintext password authenticating
202 - (NSDictionary *)login:(NSString *)_login password:(NSString *)_passwd {
203 if ((_login == nil) || (_passwd == nil))
206 [self->login release]; self->login = nil;
207 [self->password release]; self->password = nil;
209 self->login = [_login copy];
210 self->password = [_passwd copy];
215 [self closeConnection];
216 [self openConnection];
220 - (NSDictionary *)login {
221 NGHashMap *map = nil;
227 logLen = [self->login cStringLength];
228 bufLen = (logLen*2) + [self->password cStringLength] +2;
230 buf = calloc(sizeof(char), bufLen);
232 sprintf(buf, "%s %s %s",
233 [self->login cString], [self->login cString],
234 [self->password cString]);
237 buf[logLen*2+1] = '\0';
239 auth = [NSData dataWithBytesNoCopy:buf length:bufLen];
240 auth = [auth dataByEncodingBase64];
242 if (LOG_PASSWORD == 1) {
243 map = [self processCommand:[NSString stringWithFormat:
244 @"AUTHENTICATE \"PLAIN\" {%d+}\r\n%s",
245 [auth length], [auth bytes]]];
248 map = [self processCommand:[NSString stringWithFormat:
249 @"AUTHENTICATE \"PLAIN\" {%d+}\r\n%s",
250 [auth length], [auth bytes]]
251 logText:@"AUTHENTICATE \"PLAIN\" {%d+}\r\nLOGIN:PASSWORD\r\n"];
253 return [self normalizeResponse:map];
257 ** logout from the connected host and close the connection
260 - (NSDictionary *)logout {
261 NGHashMap *map = [self processCommand:@"logout"];
263 [self closeConnection];
264 return [self normalizeResponse:map];
267 - (NSDictionary *)getScript:(NSString *)_scriptName {
268 [self notImplemented:_cmd];
272 - (BOOL)isValidScriptName:(NSString *)_name {
273 return ([_name length] == 0) ? NO : YES;
276 - (NSDictionary *)putScript:(NSString *)_name script:(NSString *)_script {
280 if (![self isValidScriptName:_name]) {
281 [self logWithFormat:@"%s: missing script-name", __PRETTY_FUNCTION__];
284 if ([_script length] == 0) {
285 [self logWithFormat:@"%s: missing script", __PRETTY_FUNCTION__];
289 s = [NSString stringWithFormat:
290 @"PUTSCRIPT \"%@\" {%d+}\r\n%@\r\n",
291 _name, [_script length], _script];
292 map = [self processCommand:s];
293 return [self normalizeResponse:map];
296 - (NSDictionary *)setActiveScript:(NSString *)_name {
299 if (![self isValidScriptName:_name]) {
300 NSLog(@"%s: missing script-name", __PRETTY_FUNCTION__);
303 map = [self processCommand:
304 [NSString stringWithFormat:@"SETACTIVE \"%@\"\r\n", _name]];
305 return [self normalizeResponse:map];
308 - (NSDictionary *)deleteScript:(NSString *)_name {
312 if (![self isValidScriptName:_name]) {
313 NSLog(@"%s: missing script-name", __PRETTY_FUNCTION__);
317 s = [NSString stringWithFormat:@"DELETESCRIPT \"%@\"\r\n", _name];
318 map = [self processCommand:s];
319 return [self normalizeResponse:map];
321 - (NSDictionary *)listScript:(NSString *)_script {
322 [self notImplemented:_cmd];
328 ** Filter for all responses
329 ** result : NSNumber (response result)
330 ** exists : NSNumber (number of exists mails in selectet folder
331 ** recent : NSNumber (number of recent mails in selectet folder
332 ** expunge : NSArray (message sequence number of expunged mails
336 - (NSMutableDictionary *)normalizeResponse:(NGHashMap *)_map {
337 id keys[3], values[3];
338 NSParameterAssert(_map != nil);
340 keys[0] = @"RawResponse"; values[0] = _map;
342 values[1] = [[_map objectForKey:@"ok"] boolValue] ? YesNumber : NoNumber;
343 return [NSMutableDictionary dictionaryWithObjects:values
344 forKeys:keys count:2];
348 ** filter for open connection
351 - (NSDictionary *)normalizeOpenConnectionResponse:(NGHashMap *)_map {
352 NSMutableDictionary *result;
355 result = [self normalizeResponse:_map];
357 if (![[[_map objectEnumeratorForKey:@"ok"] nextObject] boolValue])
360 if ((tmp = [_map objectForKey:@"implementation"]))
361 [result setObject:tmp forKey:@"server"];
362 if ((tmp = [_map objectForKey:@"sieve"]))
363 [result setObject:tmp forKey:@"capabilities"];
371 ** list : NSDictionary (folder name as key and flags as value)
374 - (NSString *)description {
375 return [NSString stringWithFormat:@"<Imap4Client[0x%08X]: socket=%@>",
376 (unsigned)self, [self socket]];
379 /* Private Methods */
381 - (BOOL)handleProcessException:(NSException *)_exception
382 repetitionCount:(int)_cnt
389 if ([_exception isKindOfClass:[NGIOException class]]) {
391 @"WARNING: got exception try to restore connection: %@",
395 if ([_exception isKindOfClass:[NGImap4ParserException class]]) {
397 @"WARNING: Got Parser-Exception try to restore connection: %@",
406 - (void)waitPriorReconnectWithRepetitionCount:(int)_cnt {
410 [self logWithFormat:@"reconnect to %@, sleeping %d seconds ...",
411 self->address, timeout];
413 [self logWithFormat:@"reconnect ..."];
416 - (NGHashMap *)processCommand:(NSString *)_command logText:(NSString *)_txt {
417 NGHashMap *map = nil;
418 BOOL repeatCommand = NO;
424 if (ProfileImapEnabled) {
425 gettimeofday(&tv, NULL);
426 ti = (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0);
427 fprintf(stderr, "{");
429 do { /* TODO: shouldn't that be a while loop? */
432 [self waitPriorReconnectWithRepetitionCount:repeatCnt];
440 [self sendCommand:_command logText:_txt];
441 map = [self->parser parseSieveResponse];
444 repeatCommand = [self handleProcessException:localException
445 repetitionCount:repeatCnt];
449 while (repeatCommand);
451 if (ProfileImapEnabled) {
452 gettimeofday(&tv, NULL);
453 ti = (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0) - ti;
454 fprintf(stderr, "}[%s] <Send Command> : time needed: %4.4fs\n",
455 __PRETTY_FUNCTION__, ti < 0.0 ? -1.0 : ti);
461 - (NGHashMap *)processCommand:(NSString *)_command {
462 return [self processCommand:_command logText:_command];
465 - (void)sendCommand:(NSString *)_command logText:(NSString *)_txt {
466 NSString *command = nil;
471 fprintf(stderr, "C: %s\n", [_txt cString]);
473 [self->text writeString:command];
474 [self->text writeString:@"\r\n"];
478 - (void)sendCommand:(NSString *)_command {
479 [self sendCommand:_command logText:_command];
482 @end /* NGSieveClient */