]> err.no Git - sope/blob - sope-mime/NGMail/NGSendMail.m
minor code cleanups
[sope] / sope-mime / NGMail / NGSendMail.m
1 /*
2   Copyright (C) 2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo 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
9   later version.
10
11   OGo 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.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "NGSendMail.h"
23 #include "NGMimeMessageGenerator.h"
24 #include "NGMailAddressParser.h"
25 #include "NGMailAddress.h"
26 #include "common.h"
27
28 // TODO: this class is derived from the LSMailDeliverCommand in OGo Logic,
29 //       it still needs a lot of cleanup
30
31 @implementation NGSendMail
32
33 + (id)sharedSendMail {
34   static NGSendMail *sendmail = nil; // THREAD
35   if (sendmail == nil)
36     sendmail = [[self alloc] init];
37   return sendmail;
38 }
39
40 - (id)initWithExecutablePath:(NSString *)_path {
41   if ((self = [super init])) {
42     NSUserDefaults *ud;
43     
44     ud = [NSUserDefaults standardUserDefaults];
45     
46     self->isLoggingEnabled = 
47       [ud boolForKey:@"ImapDebugEnabled"];
48     self->shouldOnlyUseMailboxName =
49       [ud boolForKey:@"UseOnlyMailboxNameForSendmail"];
50     
51     if ([_path isNotNull])
52       self->executablePath = [_path copy];
53     else {
54 #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
55       self->executablePath = @"/usr/sbin/sendmail";
56 #else
57       self->executablePath = @"/usr/lib/sendmail";
58 #endif
59     }
60   }
61   return self;
62 }
63 - (id)init {
64   NSString *p;
65   
66   p = [[NSUserDefaults standardUserDefaults] stringForKey:@"SendmailPath"];
67   return [self initWithExecutablePath:p];
68 }
69
70 - (void)dealloc {
71   [self->executablePath release];
72   [super dealloc];
73 }
74
75 /* accessors */
76
77 - (NSString *)executablePath {
78   return self->executablePath;
79 }
80
81 - (BOOL)isSendLoggingEnabled {
82   return self->isLoggingEnabled;
83 }
84 - (BOOL)shouldOnlyUseMailboxName {
85   return self->shouldOnlyUseMailboxName;
86 }
87
88 /* operations */
89
90 - (BOOL)isSendMailAvailable {
91   NSFileManager *fm;
92   
93   fm = [NSFileManager defaultManager];
94   return [fm isExecutableFileAtPath:[self executablePath]];
95 }
96
97 /* errors */
98
99 - (NSException *)missingMailToSendError {
100   return [NSException exceptionWithName:@"NGSendMailException"
101                       reason:@"missing mail content to send"
102                       userInfo:nil];
103 }
104 - (NSException *)cannotWriteTemporaryFileError {
105   return [NSException exceptionWithName:@"NGSendMailException"
106                       reason:@"failed to write temporary mail file"
107                       userInfo:nil];
108 }
109 - (NSException *)failedToStartSendMailError:(int)_errorCode {
110   return [NSException exceptionWithName:@"NGSendMailException"
111                       reason:@"failed to start sendmail tool"
112                       userInfo:nil];
113 }
114 - (NSException *)failedToSendFileToSendMail:(NSString *)_path {
115   return [NSException exceptionWithName:@"NGSendMailException"
116                       reason:@"failed to send message file to sendmail tool"
117                       userInfo:nil];
118 }
119 - (NSException *)failedToSendDataToSendMail:(NSData *)_data {
120   return [NSException exceptionWithName:@"NGSendMailException"
121                       reason:@"failed to send message data to sendmail tool"
122                       userInfo:nil];
123 }
124
125 - (NSException *)_errorExceptionWithReason:(NSString *)_reason {
126   return [NSException exceptionWithName:@"NGSendMailException"
127                       reason:_reason
128                       userInfo:nil];
129 #if 0 // TODO: in LSMailDeliverCommand, check whether someone depends on it
130   return [LSDBObjectCommandException exceptionWithStatus:NO
131                                      object:self
132                                      reason:_reason userInfo:nil];
133 #endif
134 }
135
136 - (NSException *)_handleSendMailErrorCode:(int)_ec sendmail:(NSString *)_cmdl {
137   if (_ec == 32512) {
138     NSString *str;
139     
140     str = [@"NoExecutableSendmailBinary " stringByAppendingString:
141               [self executablePath]];
142     [self logWithFormat:@"%@ is no executable file", [self executablePath]];
143     return [self _errorExceptionWithReason:str];
144   }
145   if (_ec == 17664) {
146     [self logWithFormat:@"sendmail: message file too big!"];
147     return [self _errorExceptionWithReason:@"MessageFileTooBig"];
148   }
149   
150   [self logWithFormat:@"[1] Could not write mail to sendmail! <%d>",_ec];
151   return [self _errorExceptionWithReason:@"FailedToSendMail"];
152 }
153
154 /* temporary file */
155
156 - (void)_removeMailTmpFile:(NSString *)_path {
157   if ([_path length] < 2)
158     return;
159   
160   [[NSFileManager defaultManager] removeFileAtPath:_path handler:nil];
161 }
162
163 - (NSString *)_generateTemporaryFileForPart:(id<NGMimePart>)_part {
164   NGMimeMessageGenerator *gen;
165   NSString *p;
166   
167   gen = [[NGMimeMessageGenerator alloc] init];
168   p = [[gen generateMimeFromPartToFile:_part] copy];
169   [gen release]; gen = nil;
170
171   return [p autorelease];
172 }
173
174 /* parsing mail addresses */
175
176 - (NSString *)mailAddrForStr:(NSString *)_str {
177   NGMailAddressParser *parser;
178   NGMailAddress       *addr;
179   
180   if (![self shouldOnlyUseMailboxName])
181     return _str;
182   
183   parser = nil;
184   addr   = nil;
185   
186   // TODO: make NGMailAddressParser not throw exceptions,
187   //       then remove the handler
188   NS_DURING {
189     parser = [NGMailAddressParser mailAddressParserWithString:_str];
190     addr   = [[parser parseAddressList] lastObject];
191   }
192   NS_HANDLER {
193     fprintf(stderr,"ERROR: get exception during parsing address %s\n",
194             [[localException description] cString]);
195     parser = nil;
196     addr   = nil;
197   }
198   NS_ENDHANDLER;
199
200   return (addr) ? [addr address] : _str;
201 }
202
203 /* logging */
204
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]);
208 }
209
210 - (void)_logMailSend:(NSString *)sendmail ofData:(NSData *)_data {
211   fprintf(stderr, "%s \n", [sendmail cString]);
212   
213   if ([_data length] > 5000) {
214     NSData *data;
215     
216     data = [_data subdataWithRange:NSMakeRange(0,5000)];
217     fprintf(stderr, "%s...\n", (unsigned char *)[data bytes]);
218   }
219   else
220     fprintf(stderr, "%s\n", (char *)[_data bytes]);
221 }
222
223 /* sending the mail */
224
225 - (FILE *)openStreamToSendMail:(NSString *)_cmdline {
226   return [_cmdline isNotNull] ? popen([_cmdline cString], "w") : NULL;
227 }
228 - (int)closeStreamToSendMail:(FILE *)_mail {
229   if (_mail == NULL) 
230     return 0;
231   return pclose(_mail);
232 }
233
234 - (NSMutableString *)buildSendMailCommandLineWithSender:(NSString *)_sender {
235   NSMutableString *sendmail;
236   
237   if ([[self executablePath] length] == 0)
238     return nil;
239   
240   sendmail = [NSMutableString stringWithCapacity:256];
241   [sendmail setString:[self executablePath]];
242   
243   /* don't treat a line with just "." as EOF */
244   [sendmail appendString:@" -i "];
245   
246   /* add sender when available */
247   if (_sender != nil) {
248     NSString *f;
249     
250     f = [[_sender componentsSeparatedByString:@","]
251                   componentsJoinedByString:@" "];
252     [sendmail appendString:@"-f "];
253     [sendmail appendString:f];
254     [sendmail appendString:@" "];
255   }
256   return sendmail;
257 }
258
259 - (NSException *)_handleAppendMessageException:(NSException *)_exception {
260   [self logWithFormat:@"catched exception: %@", _exception];
261   return nil;
262 }
263
264 - (BOOL)_appendMessageFile:(NSString *)_p to:(FILE *)_fd {
265   NGFileStream *fs;
266   int  fileLen;
267   BOOL result;
268
269   if (_p == nil) {
270     NSLog(@"ERROR: call %s without self->messageTmpFile",
271           __PRETTY_FUNCTION__);
272     return NO;
273   }
274   fileLen = [[[[NSFileManager defaultManager]
275                               fileAttributesAtPath:_p
276                               traverseLink:NO]
277                               objectForKey:NSFileSize] intValue];
278   
279   if (fileLen == 0) {
280     NSLog(@"ERROR[%s] missing file at path %@", __PRETTY_FUNCTION__,
281           _p);
282     return NO;
283   }
284   
285   fs = [(NGFileStream *)[NGFileStream alloc] initWithPath:_p];
286
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;
291     return NO;
292   }
293   
294   result = YES;
295   NS_DURING {
296     int  read;
297     int  alreadyRead;
298     int  bufCnt = 8192;
299     char buffer[bufCnt+1];
300
301     alreadyRead = 0;
302     
303     read = (bufCnt > (fileLen - alreadyRead))
304            ? fileLen - alreadyRead : bufCnt;
305     
306     while ((read = [fs readBytes:buffer count:read]) != 0) {
307       int rc;
308       
309       alreadyRead += read;
310       
311       rc = fwrite(buffer, read, 1, _fd);
312       if (rc == 0) {
313           fprintf(stderr, "%s: Failed to write %i bytes to process\n",
314                   __PRETTY_FUNCTION__, alreadyRead);
315           break;
316       }
317       if (alreadyRead == fileLen)
318         break;
319     }
320   }
321   NS_HANDLER {
322     [[self _handleAppendMessageException:localException] raise];
323     result = NO;
324   }
325   NS_ENDHANDLER;
326   
327   [fs release]; fs = nil;
328   return result;
329 }
330
331 - (BOOL)_appendData:(NSData *)_data to:(FILE *)_fd {
332   int written;
333   
334   if ([_data length] == 0)
335     return YES;
336   
337   written = fwrite((char *)[_data bytes], [_data length],
338                    1, _fd);
339   if (written > 0) {
340     [self logWithFormat:@"wrote %d, length %d", written, [_data length]];
341     return YES;
342   }
343   
344   [self logWithFormat:@"[2] Could not write mail to sendmail <%d>", errno];
345   
346   if ([_data length] > 5000)
347     [self logWithFormat:@"[2] message: [size: %d]", [_data length]];
348   else
349     [self logWithFormat:@"[2] message: <%s>", (char *)[_data bytes]];
350   
351   return NO;
352 }
353
354 - (void)addRecipients:(NSArray *)_recipients 
355   toCmdLine:(NSMutableString *)_cmdline
356 {
357   NSEnumerator *enumerator;
358   NSString     *str;
359   
360   enumerator = [_recipients objectEnumerator];
361   while ((str = [enumerator nextObject]) != nil) {
362     NSEnumerator *e;
363     NSString     *s;
364     
365     if ([str rangeOfString:@","].length == 0) {
366       [_cmdline appendFormat:@"'%@' ", [self mailAddrForStr:str]];
367       continue;
368     }
369     
370     e = [[str componentsSeparatedByString:@","] objectEnumerator];
371     while ((s = [e nextObject])) {
372       s = [[s componentsSeparatedByString:@"'"] componentsJoinedByString:@""];
373       s = [[s componentsSeparatedByString:@","] componentsJoinedByString:@""];
374       
375       [_cmdline appendFormat:@"'%@'", [self mailAddrForStr:s]];
376     }
377     [_cmdline appendString:@" "];
378   }
379 }
380
381 /* main entry methods */
382
383 - (NSException *)sendMailAtPath:(NSString *)_path toRecipients:(NSArray *)_to
384   sender:(NSString *)_sender
385 {
386   NSMutableString *sendmail;
387   FILE            *toMail       = NULL;
388   NSException     *error;
389   int  errorCode;
390   BOOL ok;
391   
392   if (_path == nil)
393     return [self missingMailToSendError];
394   
395   sendmail = [self buildSendMailCommandLineWithSender:_sender];
396   [self addRecipients:_to toCmdLine:sendmail];
397   
398   if ((toMail = [self openStreamToSendMail:sendmail]) == NULL)
399     return [self failedToStartSendMailError:errno];
400   
401   if ([self isSendLoggingEnabled]) [self _logMailSend:sendmail ofPath:_path];
402   
403   ok = [self _appendMessageFile:_path to:toMail];
404   
405   error = nil;
406   if ((errorCode = [self closeStreamToSendMail:toMail]) != 0) {
407     if (ok) {
408       error = [self _handleSendMailErrorCode:errorCode sendmail:sendmail];
409     }
410   }
411   if (!ok) error = [self failedToSendFileToSendMail:_path];
412   return error; /* nil means 'everything is awesome' */
413 }
414
415 - (NSException *)sendMailData:(NSData *)_data toRecipients:(NSArray *)_to
416   sender:(NSString *)_sender
417 {
418   NSMutableString *sendmail;
419   FILE            *toMail       = NULL;
420   NSException     *error;
421   int  errorCode;
422   BOOL ok;
423   
424   if (_data == nil)
425     return [self missingMailToSendError];
426   
427   sendmail = [self buildSendMailCommandLineWithSender:_sender];
428   [self addRecipients:_to toCmdLine:sendmail];
429   
430   if ((toMail = [self openStreamToSendMail:sendmail]) == NULL)
431     return [self failedToStartSendMailError:errno];
432   
433   if ([self isSendLoggingEnabled]) [self _logMailSend:sendmail ofData:_data];
434   
435   ok = [self _appendData:_data to:toMail];
436   
437   error = nil;
438   if ((errorCode = [self closeStreamToSendMail:toMail]) != 0) {
439     if (ok) {
440       error = [self _handleSendMailErrorCode:errorCode sendmail:sendmail];
441     }
442   }
443   if (!ok) error = [self failedToSendDataToSendMail:_data];
444   return error; /* nil means 'everything is awesome' */
445 }
446
447 - (NSException *)sendMimePart:(id<NGMimePart>)_pt toRecipients:(NSArray *)_to
448   sender:(NSString *)_sender
449 {
450   NSException *error;
451   NSString *tmpfile;
452   
453   if (_pt == nil)
454     return [self missingMailToSendError];
455
456   /* generate file for part */
457   
458   if ((tmpfile = [self _generateTemporaryFileForPart:_pt]) == nil)
459     return [self cannotWriteTemporaryFileError];
460
461   /* send file */
462   
463   error = [self sendMailAtPath:tmpfile toRecipients:_to sender:_sender];
464   
465   /* delete temporary file */
466   
467   [self _removeMailTmpFile:tmpfile];
468   
469   return error;
470 }
471
472 @end /* NGSendMail */