2 Copyright (C) 2004-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 "NGSendMail.h"
23 #include "NGMimeMessageGenerator.h"
24 #include "NGMailAddressParser.h"
25 #include "NGMailAddress.h"
28 // TODO: this class is derived from the LSMailDeliverCommand in OGo Logic,
29 // it still needs a lot of cleanup
31 @implementation NGSendMail
33 + (id)sharedSendMail {
34 static NGSendMail *sendmail = nil; // THREAD
36 sendmail = [[self alloc] init];
40 - (id)initWithExecutablePath:(NSString *)_path {
41 if ((self = [super init])) {
44 ud = [NSUserDefaults standardUserDefaults];
46 self->isLoggingEnabled =
47 [ud boolForKey:@"ImapDebugEnabled"];
48 self->shouldOnlyUseMailboxName =
49 [ud boolForKey:@"UseOnlyMailboxNameForSendmail"];
51 if ([_path isNotNull])
52 self->executablePath = [_path copy];
54 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
55 self->executablePath = @"/usr/sbin/sendmail";
57 self->executablePath = @"/usr/lib/sendmail";
66 p = [[NSUserDefaults standardUserDefaults] stringForKey:@"SendmailPath"];
67 return [self initWithExecutablePath:p];
71 [self->executablePath release];
77 - (NSString *)executablePath {
78 return self->executablePath;
81 - (BOOL)isSendLoggingEnabled {
82 return self->isLoggingEnabled;
84 - (BOOL)shouldOnlyUseMailboxName {
85 return self->shouldOnlyUseMailboxName;
90 - (BOOL)isSendMailAvailable {
93 fm = [NSFileManager defaultManager];
94 return [fm isExecutableFileAtPath:[self executablePath]];
99 - (NSException *)missingMailToSendError {
100 return [NSException exceptionWithName:@"NGSendMailException"
101 reason:@"missing mail content to send"
104 - (NSException *)cannotWriteTemporaryFileError {
105 return [NSException exceptionWithName:@"NGSendMailException"
106 reason:@"failed to write temporary mail file"
109 - (NSException *)failedToStartSendMailError:(int)_errorCode {
110 return [NSException exceptionWithName:@"NGSendMailException"
111 reason:@"failed to start sendmail tool"
114 - (NSException *)failedToSendFileToSendMail:(NSString *)_path {
115 return [NSException exceptionWithName:@"NGSendMailException"
116 reason:@"failed to send message file to sendmail tool"
119 - (NSException *)failedToSendDataToSendMail:(NSData *)_data {
120 return [NSException exceptionWithName:@"NGSendMailException"
121 reason:@"failed to send message data to sendmail tool"
125 - (NSException *)_errorExceptionWithReason:(NSString *)_reason {
126 return [NSException exceptionWithName:@"NGSendMailException"
129 #if 0 // TODO: in LSMailDeliverCommand, check whether someone depends on it
130 return [LSDBObjectCommandException exceptionWithStatus:NO
132 reason:_reason userInfo:nil];
136 - (NSException *)_handleSendMailErrorCode:(int)_ec sendmail:(NSString *)_cmdl {
140 str = [@"NoExecutableSendmailBinary " stringByAppendingString:
141 [self executablePath]];
142 [self logWithFormat:@"%@ is no executable file", [self executablePath]];
143 return [self _errorExceptionWithReason:str];
146 [self logWithFormat:@"sendmail: message file too big!"];
147 return [self _errorExceptionWithReason:@"MessageFileTooBig"];
150 [self logWithFormat:@"[1] Could not write mail to sendmail! <%d>",_ec];
151 return [self _errorExceptionWithReason:@"FailedToSendMail"];
156 - (void)_removeMailTmpFile:(NSString *)_path {
157 if ([_path length] < 2)
160 [[NSFileManager defaultManager] removeFileAtPath:_path handler:nil];
163 - (NSString *)_generateTemporaryFileForPart:(id<NGMimePart>)_part {
164 NGMimeMessageGenerator *gen;
167 gen = [[NGMimeMessageGenerator alloc] init];
168 p = [[gen generateMimeFromPartToFile:_part] copy];
169 [gen release]; gen = nil;
171 return [p autorelease];
174 /* parsing mail addresses */
176 - (NSString *)mailAddrForStr:(NSString *)_str {
177 NGMailAddressParser *parser;
180 if (![self shouldOnlyUseMailboxName])
186 // TODO: make NGMailAddressParser not throw exceptions,
187 // then remove the handler
189 parser = [NGMailAddressParser mailAddressParserWithString:_str];
190 addr = [[parser parseAddressList] lastObject];
193 fprintf(stderr,"ERROR: get exception during parsing address %s\n",
194 [[localException description] cString]);
200 return (addr) ? [addr address] : _str;
205 - (void)_logMailSend:(NSString *)sendmail ofPath:(NSString *)_p {
206 fprintf(stderr, "%s \n", [sendmail cString]);
207 fprintf(stderr, "read data from %s\n", [_p cString]);
210 - (void)_logMailSend:(NSString *)sendmail ofData:(NSData *)_data {
211 fprintf(stderr, "%s \n", [sendmail cString]);
213 if ([_data length] > 5000) {
216 data = [_data subdataWithRange:NSMakeRange(0,5000)];
217 fprintf(stderr, "%s...\n", (unsigned char *)[data bytes]);
220 fprintf(stderr, "%s\n", (char *)[_data bytes]);
223 /* sending the mail */
225 - (FILE *)openStreamToSendMail:(NSString *)_cmdline {
226 return [_cmdline isNotNull] ? popen([_cmdline cString], "w") : NULL;
228 - (int)closeStreamToSendMail:(FILE *)_mail {
231 return pclose(_mail);
234 - (NSMutableString *)buildSendMailCommandLineWithSender:(NSString *)_sender {
235 NSMutableString *sendmail;
237 if ([[self executablePath] length] == 0)
240 sendmail = [NSMutableString stringWithCapacity:256];
241 [sendmail setString:[self executablePath]];
243 /* don't treat a line with just "." as EOF */
244 [sendmail appendString:@" -i "];
246 /* add sender when available */
247 if (_sender != nil) {
250 f = [[_sender componentsSeparatedByString:@","]
251 componentsJoinedByString:@" "];
252 [sendmail appendString:@"-f "];
253 [sendmail appendString:f];
254 [sendmail appendString:@" "];
259 - (NSException *)_handleAppendMessageException:(NSException *)_exception {
260 [self logWithFormat:@"catched exception: %@", _exception];
264 - (BOOL)_appendMessageFile:(NSString *)_p to:(FILE *)_fd {
270 NSLog(@"ERROR: call %s without self->messageTmpFile",
271 __PRETTY_FUNCTION__);
274 fileLen = [[[[NSFileManager defaultManager]
275 fileAttributesAtPath:_p
277 objectForKey:NSFileSize] intValue];
280 NSLog(@"ERROR[%s] missing file at path %@", __PRETTY_FUNCTION__,
285 fs = [(NGFileStream *)[NGFileStream alloc] initWithPath:_p];
287 if (![fs openInMode:@"r"]) {
288 NSLog(@"ERROR[%s]: could not open file stream for temp-file for "
289 @"reading: %@", __PRETTY_FUNCTION__, _p);
290 [fs release]; fs = nil;
299 char buffer[bufCnt+1];
303 read = (bufCnt > (fileLen - alreadyRead))
304 ? fileLen - alreadyRead : bufCnt;
306 while ((read = [fs readBytes:buffer count:read]) != 0) {
311 rc = fwrite(buffer, read, 1, _fd);
313 fprintf(stderr, "%s: Failed to write %i bytes to process\n",
314 __PRETTY_FUNCTION__, alreadyRead);
317 if (alreadyRead == fileLen)
322 [[self _handleAppendMessageException:localException] raise];
327 [fs release]; fs = nil;
331 - (BOOL)_appendData:(NSData *)_data to:(FILE *)_fd {
334 if ([_data length] == 0)
337 written = fwrite((char *)[_data bytes], [_data length],
340 [self logWithFormat:@"wrote %d, length %d", written, [_data length]];
344 [self logWithFormat:@"[2] Could not write mail to sendmail <%d>", errno];
346 if ([_data length] > 5000)
347 [self logWithFormat:@"[2] message: [size: %d]", [_data length]];
349 [self logWithFormat:@"[2] message: <%s>", (char *)[_data bytes]];
354 - (void)addRecipients:(NSArray *)_recipients
355 toCmdLine:(NSMutableString *)_cmdline
357 NSEnumerator *enumerator;
360 enumerator = [_recipients objectEnumerator];
361 while ((str = [enumerator nextObject]) != nil) {
365 if ([str rangeOfString:@","].length == 0) {
366 [_cmdline appendFormat:@"'%@' ", [self mailAddrForStr:str]];
370 e = [[str componentsSeparatedByString:@","] objectEnumerator];
371 while ((s = [e nextObject])) {
372 s = [[s componentsSeparatedByString:@"'"] componentsJoinedByString:@""];
373 s = [[s componentsSeparatedByString:@","] componentsJoinedByString:@""];
375 [_cmdline appendFormat:@"'%@'", [self mailAddrForStr:s]];
377 [_cmdline appendString:@" "];
381 /* main entry methods */
383 - (NSException *)sendMailAtPath:(NSString *)_path toRecipients:(NSArray *)_to
384 sender:(NSString *)_sender
386 NSMutableString *sendmail;
393 return [self missingMailToSendError];
395 sendmail = [self buildSendMailCommandLineWithSender:_sender];
396 [self addRecipients:_to toCmdLine:sendmail];
398 if ((toMail = [self openStreamToSendMail:sendmail]) == NULL)
399 return [self failedToStartSendMailError:errno];
401 if ([self isSendLoggingEnabled]) [self _logMailSend:sendmail ofPath:_path];
403 ok = [self _appendMessageFile:_path to:toMail];
406 if ((errorCode = [self closeStreamToSendMail:toMail]) != 0) {
408 error = [self _handleSendMailErrorCode:errorCode sendmail:sendmail];
411 if (!ok) error = [self failedToSendFileToSendMail:_path];
412 return error; /* nil means 'everything is awesome' */
415 - (NSException *)sendMailData:(NSData *)_data toRecipients:(NSArray *)_to
416 sender:(NSString *)_sender
418 NSMutableString *sendmail;
425 return [self missingMailToSendError];
427 sendmail = [self buildSendMailCommandLineWithSender:_sender];
428 [self addRecipients:_to toCmdLine:sendmail];
430 if ((toMail = [self openStreamToSendMail:sendmail]) == NULL)
431 return [self failedToStartSendMailError:errno];
433 if ([self isSendLoggingEnabled]) [self _logMailSend:sendmail ofData:_data];
435 ok = [self _appendData:_data to:toMail];
438 if ((errorCode = [self closeStreamToSendMail:toMail]) != 0) {
440 error = [self _handleSendMailErrorCode:errorCode sendmail:sendmail];
443 if (!ok) error = [self failedToSendDataToSendMail:_data];
444 return error; /* nil means 'everything is awesome' */
447 - (NSException *)sendMimePart:(id<NGMimePart>)_pt toRecipients:(NSArray *)_to
448 sender:(NSString *)_sender
454 return [self missingMailToSendError];
456 /* generate file for part */
458 if ((tmpfile = [self _generateTemporaryFileForPart:_pt]) == nil)
459 return [self cannotWriteTemporaryFileError];
463 error = [self sendMailAtPath:tmpfile toRecipients:_to sender:_sender];
465 /* delete temporary file */
467 [self _removeMailTmpFile:tmpfile];
472 @end /* NGSendMail */