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]];