/* Copyright (C) 2004-2005 SKYRIX Software AG This file is part of SOPE. SOPE is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. SOPE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SOPE; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "NGSendMail.h" #include "NGMimeMessageGenerator.h" #include "NGMailAddressParser.h" #include "NGMailAddress.h" #include "common.h" // TODO: this class is derived from the LSMailDeliverCommand in OGo Logic, // it still needs a lot of cleanup @implementation NGSendMail + (id)sharedSendMail { static NGSendMail *sendmail = nil; // THREAD if (sendmail == nil) sendmail = [[self alloc] init]; return sendmail; } - (id)initWithExecutablePath:(NSString *)_path { if ((self = [super init])) { NSUserDefaults *ud; ud = [NSUserDefaults standardUserDefaults]; self->isLoggingEnabled = [ud boolForKey:@"ImapDebugEnabled"]; self->shouldOnlyUseMailboxName = [ud boolForKey:@"UseOnlyMailboxNameForSendmail"]; if ([_path isNotNull]) self->executablePath = [_path copy]; else { #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY self->executablePath = @"/usr/sbin/sendmail"; #else self->executablePath = @"/usr/lib/sendmail"; #endif } } return self; } - (id)init { NSString *p; p = [[NSUserDefaults standardUserDefaults] stringForKey:@"SendmailPath"]; return [self initWithExecutablePath:p]; } - (void)dealloc { [self->executablePath release]; [super dealloc]; } /* accessors */ - (NSString *)executablePath { return self->executablePath; } - (BOOL)isSendLoggingEnabled { return self->isLoggingEnabled; } - (BOOL)shouldOnlyUseMailboxName { return self->shouldOnlyUseMailboxName; } /* operations */ - (BOOL)isSendMailAvailable { NSFileManager *fm; fm = [NSFileManager defaultManager]; return [fm isExecutableFileAtPath:[self executablePath]]; } /* errors */ - (NSException *)missingMailToSendError { return [NSException exceptionWithName:@"NGSendMailException" reason:@"missing mail content to send" userInfo:nil]; } - (NSException *)cannotWriteTemporaryFileError { return [NSException exceptionWithName:@"NGSendMailException" reason:@"failed to write temporary mail file" userInfo:nil]; } - (NSException *)failedToStartSendMailError:(int)_errorCode { return [NSException exceptionWithName:@"NGSendMailException" reason:@"failed to start sendmail tool" userInfo:nil]; } - (NSException *)failedToSendFileToSendMail:(NSString *)_path { return [NSException exceptionWithName:@"NGSendMailException" reason:@"failed to send message file to sendmail tool" userInfo:nil]; } - (NSException *)failedToSendDataToSendMail:(NSData *)_data { return [NSException exceptionWithName:@"NGSendMailException" reason:@"failed to send message data to sendmail tool" userInfo:nil]; } - (NSException *)_errorExceptionWithReason:(NSString *)_reason { return [NSException exceptionWithName:@"NGSendMailException" reason:_reason userInfo:nil]; #if 0 // TODO: in LSMailDeliverCommand, check whether someone depends on it return [LSDBObjectCommandException exceptionWithStatus:NO object:self reason:_reason userInfo:nil]; #endif } - (NSException *)_handleSendMailErrorCode:(int)_ec sendmail:(NSString *)_cmdl { if (_ec == 32512) { NSString *str; str = [@"NoExecutableSendmailBinary " stringByAppendingString: [self executablePath]]; [self logWithFormat:@"%@ is no executable file", [self executablePath]]; return [self _errorExceptionWithReason:str]; } if (_ec == 17664) { [self logWithFormat:@"sendmail: message file too big!"]; return [self _errorExceptionWithReason:@"MessageFileTooBig"]; } [self logWithFormat:@"[1] Could not write mail to sendmail! <%d>",_ec]; return [self _errorExceptionWithReason:@"FailedToSendMail"]; } /* temporary file */ - (void)_removeMailTmpFile:(NSString *)_path { if ([_path length] < 2) return; [[NSFileManager defaultManager] removeFileAtPath:_path handler:nil]; } - (NSString *)_generateTemporaryFileForPart:(id)_part { NGMimeMessageGenerator *gen; NSString *p; gen = [[NGMimeMessageGenerator alloc] init]; p = [[gen generateMimeFromPartToFile:_part] copy]; [gen release]; gen = nil; return [p autorelease]; } /* parsing mail addresses */ - (NSString *)mailAddrForStr:(NSString *)_str { NGMailAddressParser *parser; NGMailAddress *addr; if (![self shouldOnlyUseMailboxName]) return _str; parser = nil; addr = nil; // TODO: make NGMailAddressParser not throw exceptions, // then remove the handler NS_DURING { parser = [NGMailAddressParser mailAddressParserWithString:_str]; addr = [[parser parseAddressList] lastObject]; } NS_HANDLER { fprintf(stderr,"ERROR: get exception during parsing address %s\n", [[localException description] cString]); parser = nil; addr = nil; } NS_ENDHANDLER; return (addr) ? [addr address] : _str; } /* logging */ - (void)_logMailSend:(NSString *)sendmail ofPath:(NSString *)_p { fprintf(stderr, "%s \n", [sendmail cString]); fprintf(stderr, "read data from %s\n", [_p cString]); } - (void)_logMailSend:(NSString *)sendmail ofData:(NSData *)_data { fprintf(stderr, "%s \n", [sendmail cString]); if ([_data length] > 5000) { NSData *data; data = [_data subdataWithRange:NSMakeRange(0,5000)]; fprintf(stderr, "%s...\n", (unsigned char *)[data bytes]); } else fprintf(stderr, "%s\n", (char *)[_data bytes]); } /* sending the mail */ - (FILE *)openStreamToSendMail:(NSString *)_cmdline { return [_cmdline isNotNull] ? popen([_cmdline cString], "w") : NULL; } - (int)closeStreamToSendMail:(FILE *)_mail { if (_mail == NULL) return 0; return pclose(_mail); } - (NSMutableString *)buildSendMailCommandLineWithSender:(NSString *)_sender { NSMutableString *sendmail; if ([[self executablePath] length] == 0) return nil; sendmail = [NSMutableString stringWithCapacity:256]; [sendmail setString:[self executablePath]]; /* don't treat a line with just "." as EOF */ [sendmail appendString:@" -i "]; /* add sender when available */ if (_sender != nil) { NSString *f; f = [[_sender componentsSeparatedByString:@","] componentsJoinedByString:@" "]; [sendmail appendString:@"-f "]; [sendmail appendString:f]; [sendmail appendString:@" "]; } return sendmail; } - (NSException *)_handleAppendMessageException:(NSException *)_exception { [self logWithFormat:@"catched exception: %@", _exception]; return nil; } - (BOOL)_appendMessageFile:(NSString *)_p to:(FILE *)_fd { NGFileStream *fs; int fileLen; BOOL result; if (_p == nil) { NSLog(@"ERROR: call %s without self->messageTmpFile", __PRETTY_FUNCTION__); return NO; } fileLen = [[[[NSFileManager defaultManager] fileAttributesAtPath:_p traverseLink:NO] objectForKey:NSFileSize] intValue]; if (fileLen == 0) { NSLog(@"ERROR[%s] missing file at path %@", __PRETTY_FUNCTION__, _p); return NO; } fs = [(NGFileStream *)[NGFileStream alloc] initWithPath:_p]; if (![fs openInMode:@"r"]) { NSLog(@"ERROR[%s]: could not open file stream for temp-file for " @"reading: %@", __PRETTY_FUNCTION__, _p); [fs release]; fs = nil; return NO; } result = YES; NS_DURING { int read; int alreadyRead; int bufCnt = 8192; char buffer[bufCnt+1]; alreadyRead = 0; read = (bufCnt > (fileLen - alreadyRead)) ? fileLen - alreadyRead : bufCnt; while ((read = [fs readBytes:buffer count:read]) != 0) { int rc; alreadyRead += read; rc = fwrite(buffer, read, 1, _fd); if (rc == 0) { fprintf(stderr, "%s: Failed to write %i bytes to process\n", __PRETTY_FUNCTION__, alreadyRead); break; } if (alreadyRead == fileLen) break; } } NS_HANDLER { [[self _handleAppendMessageException:localException] raise]; result = NO; } NS_ENDHANDLER; [fs release]; fs = nil; return result; } - (BOOL)_appendData:(NSData *)_data to:(FILE *)_fd { int written; if ([_data length] == 0) return YES; written = fwrite((char *)[_data bytes], [_data length], 1, _fd); if (written > 0) { [self logWithFormat:@"wrote %d, length %d", written, [_data length]]; return YES; } [self logWithFormat:@"[2] Could not write mail to sendmail <%d>", errno]; if ([_data length] > 5000) [self logWithFormat:@"[2] message: [size: %d]", [_data length]]; else [self logWithFormat:@"[2] message: <%s>", (char *)[_data bytes]]; return NO; } - (void)addRecipients:(NSArray *)_recipients toCmdLine:(NSMutableString *)_cmdline { NSEnumerator *enumerator; NSString *str; enumerator = [_recipients objectEnumerator]; while ((str = [enumerator nextObject]) != nil) { NSEnumerator *e; NSString *s; if ([str rangeOfString:@","].length == 0) { [_cmdline appendFormat:@"'%@' ", [self mailAddrForStr:str]]; continue; } e = [[str componentsSeparatedByString:@","] objectEnumerator]; while ((s = [e nextObject])) { s = [[s componentsSeparatedByString:@"'"] componentsJoinedByString:@""]; s = [[s componentsSeparatedByString:@","] componentsJoinedByString:@""]; [_cmdline appendFormat:@"'%@'", [self mailAddrForStr:s]]; } [_cmdline appendString:@" "]; } } /* main entry methods */ - (NSException *)sendMailAtPath:(NSString *)_path toRecipients:(NSArray *)_to sender:(NSString *)_sender { NSMutableString *sendmail; FILE *toMail = NULL; NSException *error; int errorCode; BOOL ok; if (_path == nil) return [self missingMailToSendError]; sendmail = [self buildSendMailCommandLineWithSender:_sender]; [self addRecipients:_to toCmdLine:sendmail]; if ((toMail = [self openStreamToSendMail:sendmail]) == NULL) return [self failedToStartSendMailError:errno]; if ([self isSendLoggingEnabled]) [self _logMailSend:sendmail ofPath:_path]; ok = [self _appendMessageFile:_path to:toMail]; error = nil; if ((errorCode = [self closeStreamToSendMail:toMail]) != 0) { if (ok) { error = [self _handleSendMailErrorCode:errorCode sendmail:sendmail]; } } if (!ok) error = [self failedToSendFileToSendMail:_path]; return error; /* nil means 'everything is awesome' */ } - (NSException *)sendMailData:(NSData *)_data toRecipients:(NSArray *)_to sender:(NSString *)_sender { NSMutableString *sendmail; FILE *toMail = NULL; NSException *error; int errorCode; BOOL ok; if (_data == nil) return [self missingMailToSendError]; sendmail = [self buildSendMailCommandLineWithSender:_sender]; [self addRecipients:_to toCmdLine:sendmail]; if ((toMail = [self openStreamToSendMail:sendmail]) == NULL) return [self failedToStartSendMailError:errno]; if ([self isSendLoggingEnabled]) [self _logMailSend:sendmail ofData:_data]; ok = [self _appendData:_data to:toMail]; error = nil; if ((errorCode = [self closeStreamToSendMail:toMail]) != 0) { if (ok) { error = [self _handleSendMailErrorCode:errorCode sendmail:sendmail]; } } if (!ok) error = [self failedToSendDataToSendMail:_data]; return error; /* nil means 'everything is awesome' */ } - (NSException *)sendMimePart:(id)_pt toRecipients:(NSArray *)_to sender:(NSString *)_sender { NSException *error; NSString *tmpfile; if (_pt == nil) return [self missingMailToSendError]; /* generate file for part */ if ((tmpfile = [self _generateTemporaryFileForPart:_pt]) == nil) return [self cannotWriteTemporaryFileError]; /* send file */ error = [self sendMailAtPath:tmpfile toRecipients:_to sender:_sender]; /* delete temporary file */ [self _removeMailTmpFile:tmpfile]; return error; } @end /* NGSendMail */