From ed2dd9c44d8ed3d09a571b26dc0e3f2e52b12c55 Mon Sep 17 00:00:00 2001 From: helge Date: Wed, 27 Oct 2004 13:14:04 +0000 Subject: [PATCH] added NGSendMail object git-svn-id: http://svn.opengroupware.org/SOPE/trunk@317 e4a50df8-12e2-0310-a44c-efbce7f8a7e3 --- sope-mime/ChangeLog | 4 + sope-mime/NGMail/ChangeLog | 5 + sope-mime/NGMail/GNUmakefile | 2 + sope-mime/NGMail/NGSendMail.h | 67 +++++ sope-mime/NGMail/NGSendMail.m | 459 ++++++++++++++++++++++++++++++ sope-mime/NGMime/NGMimeFileData.h | 19 +- sope-mime/NGMime/NGMimeFileData.m | 13 +- sope-mime/Version | 2 +- 8 files changed, 560 insertions(+), 11 deletions(-) create mode 100644 sope-mime/NGMail/NGSendMail.h create mode 100644 sope-mime/NGMail/NGSendMail.m diff --git a/sope-mime/ChangeLog b/sope-mime/ChangeLog index 5b338749..23d7a8ff 100644 --- a/sope-mime/ChangeLog +++ b/sope-mime/ChangeLog @@ -1,3 +1,7 @@ +2004-10-27 Helge Hess + + * NGMail: added NGSendMail object (v4.3.190) + 2004-10-08 Helge Hess * NGImap4: minor improvement in an error condition (v4.3.189) diff --git a/sope-mime/NGMail/ChangeLog b/sope-mime/NGMail/ChangeLog index 903d363c..fe0616bf 100644 --- a/sope-mime/NGMail/ChangeLog +++ b/sope-mime/NGMail/ChangeLog @@ -1,3 +1,8 @@ +2004-10-27 Helge Hess + + * added new NGSendMail object (derived from mail-deliver command in + OGo Logic) + 2004-10-04 Marcus Mueller * NGMail.xcode: updated to the current build version diff --git a/sope-mime/NGMail/GNUmakefile b/sope-mime/NGMail/GNUmakefile index a3845bb5..5f250f5f 100644 --- a/sope-mime/NGMail/GNUmakefile +++ b/sope-mime/NGMail/GNUmakefile @@ -24,6 +24,7 @@ NGMail_HEADER_FILES = \ NGPop3Support.h \ NGSmtpClient.h \ NGSmtpSupport.h \ + NGSendMail.h \ NGMail_OBJC_FILES = \ NGMail.m \ @@ -38,6 +39,7 @@ NGMail_OBJC_FILES = \ NGPop3Support.m \ NGSmtpClient.m \ NGSmtpSupport.m \ + NGSendMail.m \ \ NGMimeMessageBodyGenerator.m \ NGMimeMessageMultipartBodyGenerator.m \ diff --git a/sope-mime/NGMail/NGSendMail.h b/sope-mime/NGMail/NGSendMail.h new file mode 100644 index 00000000..e4b5fb25 --- /dev/null +++ b/sope-mime/NGMail/NGSendMail.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 2004 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + OGo 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. + + OGo 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 OGo; see the file COPYING. If not, write to the + Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. +*/ + +#ifndef __LSMail_NGSendMail_H__ +#define __LSMail_NGSendMail_H__ + +#import +#include + +/* + NGSendMail + + An interface to the local sendmail binary for deliverying mail. +*/ + +@class NSString, NSArray, NSData, NSException; + +@interface NGSendMail : NSObject +{ + NSString *executablePath; + BOOL isLoggingEnabled; + BOOL shouldOnlyUseMailboxName; +} + ++ (id)sharedSendMail; + +- (id)initWithExecutablePath:(NSString *)_path; + +/* accessors */ + +- (NSString *)executablePath; + +- (BOOL)isSendLoggingEnabled; +- (BOOL)shouldOnlyUseMailboxName; + +/* operations */ + +- (BOOL)isSendMailAvailable; + +- (NSException *)sendMailAtPath:(NSString *)_path toRecipients:(NSArray *)_to + sender:(NSString *)_sender; +- (NSException *)sendMimePart:(id)_pt toRecipients:(NSArray *)_to + sender:(NSString *)_sender; +- (NSException *)sendMailData:(NSData *)_data toRecipients:(NSArray *)_to + sender:(NSString *)_sender; + +@end + +#endif /* __LSMail_NGSendMail_H__ */ diff --git a/sope-mime/NGMail/NGSendMail.m b/sope-mime/NGMail/NGSendMail.m new file mode 100644 index 00000000..c379b4c0 --- /dev/null +++ b/sope-mime/NGMail/NGSendMail.m @@ -0,0 +1,459 @@ +/* + Copyright (C) 2004 SKYRIX Software AG + + This file is part of OpenGroupware.org. + + OGo 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. + + OGo 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 OGo; 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; + + self->isLoggingEnabled = + [ud boolForKey:@"ImapDebugEnabled"]; + self->shouldOnlyUseMailboxName = + [ud boolForKey:@"UseOnlyMailboxNameForSendmail"]; + + self->executablePath = + [_path isNotNull] ? [_path copy] : @"/usr/lib/sendmail"; + } + 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 { + return popen([[self executablePath] cString], "w"); +} +- (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])) { + alreadyRead += read; + + buffer[read] = '\0'; + + if (fputs(buffer, _fd) == EOF) { + 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) + 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]) == 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]) == 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 */ diff --git a/sope-mime/NGMime/NGMimeFileData.h b/sope-mime/NGMime/NGMimeFileData.h index 1f92abf8..1142314e 100644 --- a/sope-mime/NGMime/NGMimeFileData.h +++ b/sope-mime/NGMime/NGMimeFileData.h @@ -1,7 +1,7 @@ /* - Copyright (C) 2000-2003 SKYRIX Software AG + Copyright (C) 2000-2004 SKYRIX Software AG - This file is part of OGo + This file is part of OpenGroupware.org. OGo 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 @@ -18,13 +18,23 @@ Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -// $Id$ #ifndef __NGMime_NGMimeFileData_H__ #define __NGMime_NGMimeFileData_H__ #import +/* + NGMimeFileData + + TODO: explain. Somehow this is an object to save loading an NSData to RAM. + + Checked in: + - NGMimeBodyGenerator + - NGMimeBodyPart + - NGMimeJoinedData +*/ + @class NSString; @interface NGMimeFileData : NSData @@ -34,9 +44,10 @@ int length; } - - (id)initWithPath:(NSString *)_path removeFile:(BOOL)_remove; +/* operations */ + - (BOOL)appendDataToFileDesc:(int)_fd; @end /* NGMimeFileData */ diff --git a/sope-mime/NGMime/NGMimeFileData.m b/sope-mime/NGMime/NGMimeFileData.m index 62150d96..5745b496 100644 --- a/sope-mime/NGMime/NGMimeFileData.m +++ b/sope-mime/NGMime/NGMimeFileData.m @@ -18,7 +18,6 @@ Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -// $Id$ #include "NGMimeFileData.h" #include "common.h" @@ -115,11 +114,6 @@ static unsigned tmpmask = 0600; return self->length; } -- (NSString *)description { - return [NSString stringWithFormat:@"<0x%08X[%@]: path=%@>", - self, NSStringFromClass([self class]), self->path]; -} - - (BOOL)appendDataToFileDesc:(int)_fd { NGFileStream *fs; int bufCnt = 8192; @@ -177,4 +171,11 @@ static unsigned tmpmask = 0600; return result; } +/* description */ + +- (NSString *)description { + return [NSString stringWithFormat:@"<0x%08X[%@]: path=%@>", + self, NSStringFromClass([self class]), self->path]; +} + @end /* NGMimeFileData */ diff --git a/sope-mime/Version b/sope-mime/Version index 1c4ffc5e..af2f5a3f 100644 --- a/sope-mime/Version +++ b/sope-mime/Version @@ -2,6 +2,6 @@ MAJOR_VERSION:=4 MINOR_VERSION:=3 -SUBMINOR_VERSION:=189 +SUBMINOR_VERSION:=190 # v4.2.149 requires libNGStreams v4.2.34 -- 2.39.5