2
Copyright (C) 2000-2005 SKYRIX Software AG
4
This file is part of SOPE.
6
SOPE 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
SOPE 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 SOPE; see the file COPYING. If not, write to the
18
Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22
#include "NGSmtpClient.h"
23
#include "NGSmtpSupport.h"
24
#include "NGSmtpReplyCodes.h"
27
@interface NGSmtpClient(PrivateMethods)
28
- (void)_fetchExtensionInfo;
31
@implementation NGSmtpClient
39
s = [NGActiveSocket socketInDomain:[NGInternetSocketDomain domain]];
40
return [[[self alloc] initWithSocket:s] autorelease];
44
NSLog(@"%@: init not supported, use initWithSocket: ..", self);
49
- (id)initWithSocket:(id<NGActiveSocket>)_socket { // designated initializer
50
if ((self = [super init])) {
51
self->socket = [_socket retain];
52
NSAssert(self->socket, @"invalid socket parameter");
54
[self setDebuggingEnabled:YES];
57
[(NGBufferedStream *)[NGBufferedStream alloc] initWithSource:_socket];
59
[(NGCTextStream *)[NGCTextStream alloc] initWithSource:self->connection];
61
self->state = [self->socket isConnected]
62
? NGSmtpState_connected
63
: NGSmtpState_unconnected;
70
[self->connection release];
71
[self->socket release];
77
- (id<NGActiveSocket>)socket {
81
- (NGSmtpState)state {
85
- (void)setDebuggingEnabled:(BOOL)_flag {
86
self->isDebuggingEnabled = _flag;
88
- (BOOL)isDebuggingEnabled {
89
return self->isDebuggingEnabled;
94
- (BOOL)connectToHost:(id)_host {
95
return [self connectToAddress:
96
[NGInternetSocketAddress addressWithService:@"smtp"
97
onHost:_host protocol:@"tcp"]];
100
- (BOOL)connectToAddress:(id<NGSocketAddress>)_address {
101
NGSmtpResponse *greeting = nil;
103
[self requireState:NGSmtpState_unconnected];
105
if (self->isDebuggingEnabled)
106
[NGTextErr writeFormat:@"C: connect to %@\n", _address];
108
[self->socket connectToAddress:_address];
110
// receive greetings from server
111
greeting = [self receiveReply];
112
if (self->isDebuggingEnabled)
113
[NGTextErr writeFormat:@"S: %@\n", greeting];
115
if ([greeting isPositive]) {
116
[self gotoState:NGSmtpState_connected];
117
[self _fetchExtensionInfo];
119
if (self->isDebuggingEnabled) {
120
if (self->extensions.hasPipelining)
121
[NGTextErr writeFormat:@"S: pipelining extension supported.\n"];
122
if (self->extensions.hasSize)
123
[NGTextErr writeFormat:@"S: size extension supported.\n"];
124
if (self->extensions.hasHelp)
125
[NGTextErr writeFormat:@"S: help extension supported.\n"];
126
if (self->extensions.hasExpand)
127
[NGTextErr writeFormat:@"S: expand extension supported.\n"];
140
[self gotoState:NGSmtpState_unconnected];
145
- (void)requireState:(NGSmtpState)_state {
146
if (_state != [self state]) {
147
[NSException raise:@"SMTPException"
148
format:@"require state %i, now in %i", _state, [self state]];
151
- (void)denyState:(NGSmtpState)_state {
152
if ([self state] == _state) {
153
[NSException raise:@"SMTPException"
154
format:@"not allowed in state %i", [self state]];
158
- (void)gotoState:(NGSmtpState)_state {
159
self->state = _state;
162
- (BOOL)isTransactionInProgress {
163
return (self->state == NGSmtpState_TRANSACTION);
165
- (void)abortTransaction {
166
[self gotoState:NGSmtpState_connected];
171
- (NGSmtpResponse *)receiveReply {
172
NSMutableString *desc = nil;
173
NSString *line = nil;
174
NGSmtpReplyCode code = -1;
176
line = [self->text readLineAsString];
177
if ([line length] < 4) {
178
NSLog(@"SMTP: reply has invalid format (%@)", line);
181
code = [[line substringToIndex:3] intValue];
182
//if (self->isDebuggingEnabled)
183
// [NGTextErr writeFormat:@"S: reply with code %i follows ..\n", code];
185
desc = [NSMutableString stringWithCapacity:[line length]];
186
while ([line characterAtIndex:3] == '-') {
187
if ([line length] < 4) {
188
NSLog(@"SMTP: reply has invalid format (text=%@, line=%@)", desc, line);
191
[desc appendString:[line substringFromIndex:4]];
192
[desc appendString:@"\n"];
193
line = [self->text readLineAsString];
195
if ([line length] >= 4)
196
[desc appendString:[line substringFromIndex:4]];
198
return [NGSmtpResponse responseWithCode:code text:desc];
203
- (NGSmtpResponse *)sendCommand:(NSString *)_command {
204
if (self->isDebuggingEnabled) {
205
[NGTextOut writeFormat:@"C: %@\n", _command];
209
[text writeString:_command];
210
[text writeString:@"\r\n"];
212
return [self receiveReply];
214
- (NGSmtpResponse *)sendCommand:(NSString *)_command argument:(NSString *)_argument {
215
if (self->isDebuggingEnabled) {
216
[NGTextOut writeFormat:@"C: %@ %@\n", _command, _argument];
220
[text writeString:_command];
221
[text writeString:@" "];
222
[text writeString:_argument];
223
[text writeString:@"\r\n"];
225
return [self receiveReply];
230
- (void)_fetchExtensionInfo {
231
NGSmtpResponse *reply = nil;
232
NSString *hostName = nil;
234
hostName = [(NGInternetSocketAddress *)[self->socket localAddress] hostName];
236
reply = [self sendCommand:@"EHLO" argument:hostName];
237
if ([reply code] == NGSmtpActionCompleted) {
238
NSEnumerator *lines = [[[reply text] componentsSeparatedByString:@"\n"]
240
NSString *line = nil;
242
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
244
while ((line = [lines nextObject])) {
245
if ([line hasPrefix:@"EXPN"])
246
self->extensions.hasExpand = YES;
247
else if ([line hasPrefix:@"SIZE"])
248
self->extensions.hasSize = YES;
249
else if ([line hasPrefix:@"PIPELINING"])
250
self->extensions.hasPipelining = YES;
251
else if ([line hasPrefix:@"HELP"])
252
self->extensions.hasHelp = YES;
257
if (self->isDebuggingEnabled) {
258
[NGTextErr writeFormat:@"S: %@\n", reply];
259
[NGTextErr writeFormat:@" .. could not get extension info.\n"];
264
- (BOOL)_simpleServiceCommand:(NSString *)_command expectCode:(NGSmtpReplyCode)_code {
265
NGSmtpResponse *reply = nil;
267
[self denyState:NGSmtpState_unconnected];
269
reply = [self sendCommand:_command];
270
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
271
if ([reply isPositive]) {
272
if ([reply code] != _code)
273
NSLog(@"SMTP(%@): expected reply code %i, got code %i ..",
274
_command, _code, [reply code]);
281
NGSmtpResponse *reply = nil;
283
[self requireState:NGSmtpState_connected];
285
reply = [self sendCommand:@"QUIT"];
286
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
287
if ([reply isPositive]) {
288
unsigned int waitBytes = 0;
290
if ([reply code] == NGSmtpServiceClosingChannel) {
291
// wait for connection close ..
292
while ([self->connection readByte] != -1)
296
NSLog(@"SMTP(QUIT): unexpected reply code (%i), disconnecting ..", [reply code]);
302
- (BOOL)helloWithHostname:(NSString *)_host {
303
NGSmtpResponse *reply = nil;
305
[self denyState:NGSmtpState_unconnected];
307
reply = [self sendCommand:@"HELO" argument:_host];
308
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
309
if ([reply isPositive]) {
310
if ([reply code] != NGSmtpActionCompleted) {
311
NSLog(@"SMTP(HELO): expected reply code %i, got code %i ..",
312
NGSmtpActionCompleted, [reply code]);
319
NSString *hostName = nil;
320
hostName = [(NGInternetSocketAddress *)[self->socket localAddress] hostName];
321
return [self helloWithHostname:hostName];
325
return [self _simpleServiceCommand:@"NOOP" expectCode:NGSmtpActionCompleted];
329
if ([self _simpleServiceCommand:@"RSET" expectCode:NGSmtpActionCompleted]) {
330
if ([self isTransactionInProgress])
331
[self abortTransaction];
339
NGSmtpResponse *reply = nil;
341
[self denyState:NGSmtpState_unconnected];
343
reply = [self sendCommand:@"HELP"];
344
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
345
if ([reply isPositive]) {
346
if ([reply code] != NGSmtpHelpMessage) {
347
NSLog(@"SMTP(HELP): expected reply code %i, got code %i ..",
348
NGSmtpHelpMessage, [reply code]);
354
- (NSString *)helpForTopic:(NSString *)_topic {
355
NGSmtpResponse *reply = nil;
356
[self denyState:NGSmtpState_unconnected];
358
reply = [self sendCommand:@"HELP" argument:_topic];
359
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
360
if ([reply isPositive]) {
361
if ([reply code] != NGSmtpHelpMessage) {
362
NSLog(@"SMTP(HELP): expected reply code %i, got code %i ..",
363
NGSmtpHelpMessage, [reply code]);
370
- (BOOL)verifyAddress:(id)_address {
371
NGSmtpResponse *reply = nil;
372
[self denyState:NGSmtpState_unconnected];
374
reply = [self sendCommand:@"VRFY" argument:[_address stringValue]];
375
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
376
if ([reply isPositive]) {
377
if ([reply code] != NGSmtpActionCompleted) {
378
NSLog(@"SMTP(VRFY): expected reply code %i, got code %i ..",
379
NGSmtpActionCompleted, [reply code]);
383
else if ([reply code] == NGSmtpMailboxNotFound) {
387
NSLog(@"SMTP(VRFY): expected positive or 550 reply code, got code %i ..", [reply code]);
392
// transaction commands
394
- (BOOL)mailFrom:(id)_sender {
395
NGSmtpResponse *reply = nil;
396
NSString *sender = nil;
397
[self requireState:NGSmtpState_connected];
399
sender = [@"FROM:" stringByAppendingString:[_sender stringValue]];
400
reply = [self sendCommand:@"MAIL" argument:sender];
401
if ([reply isPositive]) {
402
if ([reply code] != NGSmtpActionCompleted) {
403
NSLog(@"SMTP(MAIL FROM): expected reply code %i, got code %i ..",
404
NGSmtpActionCompleted, [reply code]);
411
- (BOOL)recipientTo:(id)_receiver {
412
NGSmtpResponse *reply = nil;
413
NSString *rcpt = nil;
415
[self requireState:NGSmtpState_TRANSACTION];
417
rcpt = [@"TO:" stringByAppendingString:[_receiver stringValue]];
418
reply = [self sendCommand:@"RCPT" argument:rcpt];
419
if ([reply isPositive]) {
420
if ([reply code] != NGSmtpActionCompleted) {
421
NSLog(@"SMTP(RCPT TO): expected reply code %i, got code %i ..",
422
NGSmtpActionCompleted, [reply code]);
429
- (BOOL)sendData:(NSData *)_data {
430
NGSmtpResponse *reply = nil;
432
[self requireState:NGSmtpState_TRANSACTION];
434
reply = [self sendCommand:@"DATA"];
435
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
436
if (([reply code] >= 300) && ([reply code] < 400)) {
437
if ([reply code] != NGSmtpStartMailInput) {
438
NSLog(@"SMTP(DATA): expected reply code %i, got code %i ..",
439
NGSmtpStartMailInput, [reply code]);
443
if (self->isDebuggingEnabled)
444
[NGTextErr writeFormat:@"C: data(%i bytes) ..\n", [_data bytes]];
446
[self->connection safeWriteBytes:[_data bytes] count:[_data length]];
447
[self->connection safeWriteBytes:".\r\n" count:3];
448
[self->connection flush];
450
reply = [self receiveReply];
451
if (self->isDebuggingEnabled) [NGTextErr writeFormat:@"S: %@\n", reply];
452
if ([reply isPositive]) {
456
NSLog(@"SMTP(DATA): mail input failed, got code %i ..", [reply code]);
464
- (NSString *)description {
465
return [NSString stringWithFormat:@"<SMTP-Client[0x%08X]: socket=%@>",
466
(unsigned)self, [self socket]];