1/*
2**  PGPController.m
3**
4**  Copyright (c) 2001-2006 Ludovic Marcotte, Tomio Arisaka
5**  Copyright (C) 2017      Riccardo Mottola
6**
7**  Author: Ludovic Marcotte <ludovic@Sophos.ca>
8**          Tomio Arisaka <tomio-a@max.hi-ho.ne.jp>
9**          Riccardo Mottola <rm@gnu.org>
10**
11**  This program is free software; you can redistribute it and/or modify
12**  it under the terms of the GNU General Public License as published by
13**  the Free Software Foundation; either version 2 of the License, or
14**  (at your option) any later version.
15**
16**  This program is distributed in the hope that it will be useful,
17**  but WITHOUT ANY WARRANTY; without even the implied warranty of
18**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19**  GNU General Public License for more details.
20**
21**  You should have received a copy of the GNU General Public License
22**  along with this program.  If not, see <http://www.gnu.org/licenses/>.
23*/
24
25#import "PGPController.h"
26#import "PGPViewController.h"
27
28// GNUMail headers
29#import "Constants.h"
30#import "MailWindowController.h"
31#import "NSAttributedString+Extensions.h"
32#import "NSUserDefaults+Extensions.h"
33#import "PasswordPanelController.h"
34#import "Utilities.h"
35
36// Pantomime headers
37#import <Pantomime/CWConstants.h>
38#import <Pantomime/CWInternetAddress.h>
39#import <Pantomime/CWMessage.h>
40#import <Pantomime/CWMIMEMultipart.h>
41#import <Pantomime/CWMIMEUtility.h>
42#import <Pantomime/CWParser.h>
43#import <Pantomime/CWPart.h>
44#import <Pantomime/NSData+Extensions.h>
45#import <Pantomime/NSFileManager+Extensions.h>
46#import <Pantomime/NSString+Extensions.h>
47
48static PGPController *singleInstance = nil;
49
50
51#define CHECK_FOR_RAW_SOURCE() ({ \
52  if (![theMessage rawSource]) \
53    { \
54      [theMessage setProperty: theTextView  forKey: @"NSTextView"]; \
55      [theMessage setProperty: [NSNumber numberWithBool: YES]  forKey: @"Loading"]; \
56      return; \
57    } \
58})
59
60//
61// Private methods
62//
63@interface PGPController (Private)
64- (BOOL) _analyseTaskOutput: (NSMutableData *) theMutableData
65                    message: (NSMutableString *) theMessage;
66
67- (void) _decryptPart: (CWPart *) thePart
68            multipart: (BOOL) aBOOL
69              message: (CWMessage *) theMessage;
70
71- (void) _verifyPart: (CWPart *) thePart
72             allPart: (CWPart *) allPart
73           rawSource: (NSData *) rawData
74       signaturePart: (CWPart *) signPart
75             message: (CWMessage *) theMessage;
76
77- (CWMessage *) _encryptMessage: (CWMessage *) theMessage
78                      multipart: (BOOL) aBOOL;
79
80- (NSString *) _passphraseForID: (NSString *) theID;
81
82- (void) _tick;
83@end
84
85
86
87//
88// View class
89//
90@interface PGPImageView : NSView
91{
92  @private
93    NSImage *_image;
94}
95- (NSImage *) image;
96- (void) setImage: (NSImage *) theImage;
97@end
98
99@implementation PGPImageView
100
101- (NSImage *) image
102{
103  return _image;
104}
105
106- (void) setImage: (NSImage *) theImage
107{
108  // No need to retain the image here since it's retained
109  // in PGPController
110  _image = theImage;
111}
112
113@end
114
115
116//
117// Passphrase class
118//
119@interface Passphrase : NSObject
120{
121  @private
122    NSString *_value;
123    NSDate *_date;
124}
125- (id) initWithValue: (NSString *) theValue;
126- (NSString *) value;
127- (void) setValue: (NSString *) theValue;
128- (NSDate *) date;
129- (void) setDate: (NSDate *) theDate;
130@end
131
132@implementation Passphrase
133
134- (id) initWithValue: (NSString *) theValue
135{
136  self = [super init];
137
138  [self setValue: theValue];
139  [self setDate: [NSDate date]];
140
141  return self;
142}
143
144- (void) dealloc
145{
146  RELEASE(_value);
147  RELEASE(_date);
148  [super dealloc];
149}
150
151- (NSString *) value
152{
153  return _value;
154}
155
156- (void) setValue: (NSString *) theValue
157{
158  ASSIGN(_value, theValue);
159}
160
161- (NSDate *) date
162{
163  return _date;
164}
165
166- (void) setDate: (NSDate *) theDate
167{
168  ASSIGN(_date, theDate);
169}
170@end
171
172
173//
174//
175//
176@implementation PGPController
177
178- (id) initWithOwner: (id) theOwner
179{
180  self = [super init];
181  if (self)
182    {
183      NSBundle *aBundle;
184
185      owner = theOwner;
186
187      aBundle = [NSBundle bundleForClass: [self class]];
188
189      resourcePath = [aBundle resourcePath];
190      RETAIN(resourcePath);
191
192      sImage = [[NSImage alloc] initWithContentsOfFile: [NSString stringWithFormat: @"%@/signed_80.tiff", resourcePath]];
193      eImage = [[NSImage alloc] initWithContentsOfFile: [NSString stringWithFormat: @"%@/encrypted_80.tiff", resourcePath]];
194      seImage = [[NSImage alloc] initWithContentsOfFile: [NSString stringWithFormat: @"%@/signed+encrypted_80.tiff", resourcePath]];
195
196      view = [[PGPImageView alloc] init];
197
198      // We create our passphrase cache
199      passphraseCache = [[NSMutableDictionary alloc] init];
200
201      [self updateAndRestartTimer];
202
203      // We register for our notification
204      [[NSNotificationCenter defaultCenter]
205       addObserver: self
206       selector: @selector(_messageFetchCompleted:)
207       name: @"PantomimeMessageFetchCompleted"
208       object: nil];
209    }
210  return self;
211}
212
213
214//
215//
216//
217- (void) dealloc
218{
219  [[NSNotificationCenter defaultCenter] removeObserver: self];
220
221  RELEASE(resourcePath);
222
223  RELEASE(view);
224
225  RELEASE(sImage);
226  RELEASE(eImage);
227  RELEASE(seImage);
228
229  RELEASE(encrypt);
230  RELEASE(sign);
231
232  RELEASE(passphraseCache);
233
234  if (timer)
235    {
236      [timer invalidate];
237      RELEASE(timer);
238    }
239
240  [super dealloc];
241}
242
243
244//
245//
246//
247+ (id) singleInstance
248{
249  if (!singleInstance)
250    {
251      singleInstance = [[PGPController alloc] initWithOwner: nil];
252    }
253
254  return singleInstance;
255}
256
257
258//
259// access / mutation methods
260//
261- (NSString *) name
262{
263  return @"PGP";
264}
265
266
267//
268//
269//
270- (NSString *) description
271{
272  return @"This is the PGP/GPG bundle for GNUMail.";
273}
274
275
276//
277//
278//
279- (NSString *) gnumailBundleVersion
280{
281  return @"v0.9.1";
282}
283
284- (NSString *) version
285{
286  return [self gnumailBundleVersion];
287}
288
289
290//
291//
292//
293- (void) setOwner: (id) theOwner
294{
295  owner = theOwner;
296}
297
298
299//
300// UI elements
301//
302- (BOOL) hasPreferencesPanel
303{
304  return YES;
305}
306
307
308//
309//
310//
311- (PreferencesModule *) preferencesModule
312{
313  return [PGPViewController singleInstance];
314}
315
316
317//
318//
319//
320- (BOOL) hasComposeViewAccessory
321{
322  return YES;
323}
324
325
326//
327//
328//
329- (id) composeViewAccessory
330{
331  NSImage *icon;
332  NSView *aView;
333
334  aView = [[NSView alloc] initWithFrame: NSMakeRect(0,0,68,32)];
335
336  //
337  // Encrypt / clear button
338  //
339  encrypt = [[NSButton alloc] initWithFrame: NSMakeRect(0,0,32,32)];
340  [encrypt setImagePosition: NSImageOnly];
341  [encrypt setBordered: NO];
342  icon = [[NSImage alloc] initWithContentsOfFile: [NSString stringWithFormat: @"%@/clear_20.tiff", resourcePath]];
343  [encrypt setImage: icon];
344  RELEASE(icon);
345
346  [encrypt setTarget: self];
347  [encrypt setAction: @selector(encryptClicked:)];
348  [encrypt setTag: NOT_ENCRYPTED];
349
350  [aView addSubview: encrypt];
351
352  if ([[NSUserDefaults standardUserDefaults] integerForKey: @"PGPBUNDLE_ALWAYS_ENCRYPT"  default: NSOffState] == NSOnState)
353    {
354      [self encryptClicked: nil];
355    }
356
357  //
358  // Signed / Unsigned button
359  //
360  sign = [[NSButton alloc] initWithFrame: NSMakeRect(36,0,32,32)];
361  [sign setImagePosition: NSImageOnly];
362  [sign setBordered: NO];
363  icon = [[NSImage alloc] initWithContentsOfFile: [NSString stringWithFormat: @"%@/unsigned_20.tiff", resourcePath]];
364  [sign setImage: icon];
365  RELEASE(icon);
366
367  [sign setTarget: self];
368  [sign setAction: @selector(signClicked:)];
369  [sign setTag: NOT_SIGNED];
370
371  [aView addSubview: sign];
372
373  if ([[NSUserDefaults standardUserDefaults] integerForKey: @"PGPBUNDLE_ALWAYS_SIGN"  default: NSOffState] == NSOnState)
374    {
375      [self signClicked: nil];
376    }
377
378  return AUTORELEASE(aView);
379}
380
381
382//
383//
384//
385- (BOOL) hasViewingViewAccessory
386{
387  return YES;
388}
389
390
391//
392//
393//
394- (id) viewingViewAccessory
395{
396  return view;
397}
398
399
400//
401//
402//
403- (enum ViewingViewType) viewingViewAccessoryType
404{
405  return ViewingViewTypeHeaderCell;
406}
407
408//
409//
410//
411- (void) viewingViewAccessoryWillBeRemovedFromSuperview: (id) theView
412{
413
414}
415
416
417//
418//
419//
420- (void) setCurrentSuperview: (NSView *) theView
421{
422  superview = theView;
423}
424
425
426//
427//
428//
429- (NSArray *) submenuForMenu: (NSMenu *) theMenu
430{
431  return nil;
432}
433
434
435//
436//
437//
438- (NSArray *) menuItemsForMenu: (NSMenu *) theMenu
439{
440  return nil;
441}
442
443
444
445//
446// action methods
447//
448- (IBAction) encryptClicked: (id) sender
449{
450  NSImage *icon;
451
452  if ([encrypt tag] == NOT_ENCRYPTED)
453    {
454      [encrypt setTag: ENCRYPTED];
455
456      icon = [[NSImage alloc] initWithContentsOfFile:
457				[NSString stringWithFormat: @"%@/encrypted_20.tiff", resourcePath]];
458      [encrypt setImage: icon];
459      RELEASE(icon);
460    }
461  else
462    {
463      [encrypt setTag: NOT_ENCRYPTED];
464
465      icon = [[NSImage alloc] initWithContentsOfFile:
466				[NSString stringWithFormat: @"%@/clear_20.tiff", resourcePath]];
467      [encrypt setImage: icon];
468      RELEASE(icon);
469    }
470}
471
472
473//
474//
475//
476- (IBAction) signClicked: (id) sender
477{
478  NSImage *icon;
479
480  if ([sign tag] == NOT_SIGNED)
481    {
482      [sign setTag: SIGNED];
483
484      icon = [[NSImage alloc] initWithContentsOfFile:
485				[NSString stringWithFormat: @"%@/signed_20.tiff", resourcePath]];
486      [sign setImage: icon];
487      RELEASE(icon);
488    }
489  else
490    {
491      [sign setTag: NOT_SIGNED];
492
493      icon = [[NSImage alloc] initWithContentsOfFile:
494				[NSString stringWithFormat: @"%@/unsigned_20.tiff", resourcePath]];
495      [sign setImage: icon];
496      RELEASE(icon);
497    }
498}
499
500
501//
502// other methods
503//
504- (void) updateAndRestartTimer
505{
506  if (timer)
507    {
508      [timer invalidate];
509      DESTROY(timer);
510    }
511
512  if ([[NSUserDefaults standardUserDefaults] integerForKey: @"PGPBUNDLE_PASSPHRASE_EXPIRY"] == NSOnState)
513    {
514      timer = [NSTimer scheduledTimerWithTimeInterval: 60*[[NSUserDefaults standardUserDefaults]
515							    integerForKey: @"PGPBUNDLE_PASSPHRASE_EXPIRY_VALUE"]
516		       target: self
517		       selector: @selector(_tick)
518		       userInfo: nil
519		       repeats: YES];
520
521      RETAIN(timer);
522    }
523}
524
525
526//
527// Pantomime related methods
528//
529- (CWMessage *) messageWasEncoded: (CWMessage *) theMessage
530{
531  CWMessage *aMessage;
532
533  // We first verify if we must at least sign OR encrypt
534  if ([sign tag] == NOT_SIGNED && [encrypt tag] == NOT_ENCRYPTED)
535    {
536      return theMessage;
537    }
538
539  // If our content isn't a multipart
540  if ([theMessage isMIMEType: @"text"  subType: @"*"])
541    {
542      if ([[NSUserDefaults standardUserDefaults] boolForKey: @"PGPBUNDLE_ALWAYS_MULTIPART"])
543	{
544	  CWMIMEMultipart *aMimeMultipart;
545	  NSData  *aBoundaryData, *aData;
546	  CWPart *aPart;
547
548	  NSRange aRange;
549
550	  // We create our new multipart object
551	  aMimeMultipart = [[CWMIMEMultipart alloc] init];
552
553	  // We add our message part
554	  aPart = [[CWPart alloc] init];
555	  [aPart setContentTransferEncoding: [theMessage contentTransferEncoding]];
556	  [aPart setContentType: [theMessage contentType]];
557	  [aPart setCharset: [theMessage charset]];
558	  aData = [theMessage dataValue];
559	  aRange = [aData rangeOfCString: "\n\n"];
560	  aData = [aData subdataFromIndex: (aRange.location + 2)];
561
562	  if ([theMessage contentTransferEncoding] == PantomimeEncodingQuotedPrintable)
563	    {
564	      aData = [aData decodeQuotedPrintableInHeader: NO];
565	    }
566	  else if ([theMessage contentTransferEncoding] == PantomimeEncodingBase64)
567	    {
568	      aData = [aData decodeBase64];
569	    }
570
571	  [aPart setContent: aData];
572	  [aPart setSize: [aData length]];
573
574	  [aMimeMultipart addPart: aPart];
575	  RELEASE(aPart);
576
577	  // We add our dummy part
578	  aPart = [[CWPart alloc] init];
579	  [aPart setContentTransferEncoding: PantomimeEncodingNone];
580	  [aPart setContentType: @"text/plain"];
581	  [aPart setCharset: @"us-ascii"];
582	  [aPart setContentDisposition: PantomimeAttachmentDisposition];
583	  [aPart setFilename: @"RFC3156.txt"];
584	  [aPart setContent: [@"RFC3156 defines security multipart formats for MIME with OpenPGP." dataUsingEncoding: NSASCIIStringEncoding]];
585	  [aPart setSize: [(NSData *)[aPart content] length]];
586	  [aMimeMultipart addPart: aPart];
587	  RELEASE(aPart);
588
589	  // We generate a new boundary
590	  aBoundaryData = [CWMIMEUtility globallyUniqueBoundary];
591
592	  // We set the new boundary, the new Content-Type and the Content-Transfer-Encoding to our message
593	  [theMessage setBoundary: aBoundaryData];
594	  [theMessage setContentType: @"multipart/mixed"];
595	  [theMessage setContentTransferEncoding: PantomimeEncodingNone];
596
597	  // We finally set the new multipart content
598	  [theMessage setContent: aMimeMultipart];
599	  RELEASE(aMimeMultipart);
600
601	  // We got a multipart content!
602	  aMessage = [self _encryptMessage: theMessage  multipart: YES];
603
604	  return aMessage;
605	}
606
607      aMessage = [self _encryptMessage: theMessage  multipart: NO];
608    }
609  else
610    {
611      // We got a multipart content!
612      aMessage = [self _encryptMessage: theMessage  multipart: YES];
613    }
614
615  return aMessage;
616}
617
618
619//
620//
621//
622- (void) messageWasDisplayed: (CWMessage *) theMessage
623		      inView: (NSTextView *) theTextView
624{
625  id o;
626
627  o = [theMessage propertyForKey: @"Loading"];
628
629  if (o && [o boolValue])
630    {
631      [[theTextView textStorage] deleteCharactersInRange: NSMakeRange(0, [[theTextView textStorage] length])];
632      [[theTextView textStorage] insertAttributedString: [NSAttributedString attributedStringFromHeadersForMessage: theMessage
633									     showAllHeaders: NO
634									     useMailHeaderCell: YES]
635				 atIndex: 0];
636      [[theTextView textStorage] appendAttributedString: [NSAttributedString attributedStringWithString: _(@"Loading message...")
637									     attributes: nil]];
638    }
639}
640
641
642//
643// FIXME: Consider the Content-Type's protocol!
644//
645- (void) messageWillBeDisplayed: (CWMessage *) theMessage
646                         inView: (NSTextView *) theTextView
647{
648  //
649  // We DO NOT check if the message's content IS NOT a NSString. This could
650  // happen if the decoding op in Pantomime failed.
651  //
652  if ([theMessage content] &&
653      [[theMessage content] isKindOfClass: [NSData class]] &&
654      [theMessage isMIMEType: @"text"  subType: @"plain"])
655    {
656      if ([(NSData *)[theMessage content] hasCPrefix: "-----BEGIN PGP MESSAGE-----"] ||
657	  [(NSData *)[theMessage content] hasCPrefix: "-----BEGIN PGP SIGNED MESSAGE-----"])
658	{
659	  CHECK_FOR_RAW_SOURCE();
660	  [self _decryptPart: theMessage  multipart: NO  message: theMessage];
661	}
662    }
663  //
664  // VERIFY IF:
665  //             multipart/encrypted
666  //
667  //             contains exactly TWO parts: application/pgp-encrypted
668  //                                         application/octet-stream
669  //
670  //
671  else if ([theMessage isMIMEType: @"multipart"  subType: @"encrypted"])
672    {
673      CWMIMEMultipart *aMimeMultipart;
674      int i;
675
676      CHECK_FOR_RAW_SOURCE();
677
678      // We search for our octet-stream part.
679      aMimeMultipart = (CWMIMEMultipart *)[theMessage content];
680
681      for (i = ([aMimeMultipart count] - 1); i >= 0; i--)
682	{
683	  CWPart *aPart;
684
685	  aPart = [aMimeMultipart partAtIndex: i];
686
687	  if ([aPart isMIMEType: @"application"  subType: @"octet-stream"])
688	    {
689	      [self _decryptPart: aPart  multipart: YES  message: theMessage];
690	    }
691	  else if ([aPart isMIMEType: @"application"  subType: @"pgp-encrypted"])
692	    {
693	      [aMimeMultipart removePart: aPart];
694	    }
695	}
696    }
697  //
698  // VERIFY IF:
699  //             multipart/signed
700  //
701  else if ([theMessage isMIMEType: @"multipart"  subType: @"signed"])
702    {
703      CWMIMEMultipart *aMimeMultipart;
704      int i;
705
706      CHECK_FOR_RAW_SOURCE();
707
708      aMimeMultipart = (CWMIMEMultipart *)[theMessage content];
709
710      for (i = 1; i < [aMimeMultipart count]; i++)
711	{
712	  CWPart  *aPart;
713
714	  aPart = [aMimeMultipart partAtIndex: i];
715
716	  if ([aPart isMIMEType: @"application"  subType: @"pgp-signature"])
717	    {
718	      [self _verifyPart: [aMimeMultipart partAtIndex: 0]
719		    allPart: nil
720		    rawSource: nil
721		    signaturePart: aPart
722		    message: theMessage];
723	      [aMimeMultipart removePart: aPart];
724	      break;
725	    }
726        }
727    }
728
729  if ([theMessage propertyForKey: @"CONTENT-STATUS"] &&
730      [[theMessage propertyForKey: @"CONTENT-STATUS"] intValue] == ENCRYPTED)
731    {
732      [view setImage: eImage];
733    }
734  else if ([theMessage propertyForKey: @"CONTENT-STATUS"] &&
735	   [[theMessage propertyForKey: @"CONTENT-STATUS"] intValue] == SIGNED)
736    {
737      [view setImage: sImage];
738    }
739  else if ([theMessage propertyForKey: @"CONTENT-STATUS"] &&
740	   [[theMessage propertyForKey: @"CONTENT-STATUS"] intValue] == SIGNED_AND_ENCRYPTED)
741    {
742      [view setImage: seImage];
743    }
744  else
745    {
746      [view setImage: nil];
747    }
748}
749
750@end
751
752
753
754//
755// Private methods
756//
757@implementation PGPController (Private)
758
759//
760// When this method is called, all results to --status-fd=2 (stderr)
761// have been produced by the GPG task. So, we simply read it,
762// and toss the irrelevant information.
763//
764- (BOOL) _analyseTaskOutput: (NSMutableData *) theMutableData
765		    message: (NSMutableString *) theMessage
766{
767  NSArray *allLines;
768  BOOL aBOOL;
769  int i, c;
770
771  allLines = [theMutableData componentsSeparatedByCString: "\n"];
772  c = [allLines count];
773  aBOOL = YES;
774
775  for (i = 0; i < c; i++)
776    {
777      if ([[allLines objectAtIndex: i] hasCPrefix: "[GNUPG:] "])
778	{
779	  NSString *aString;
780
781	  aString = [[NSString alloc] initWithData: [[allLines objectAtIndex: i] subdataFromIndex: 9]
782				      encoding: NSUTF8StringEncoding];
783
784	  NSLog(@"READ = |%@|", aString);
785
786	  // We analyse our task's output.
787	  if ([aString hasPrefix: @"BAD_PASSPHRASE"])
788	    {
789	      [theMessage appendString: _(@"\nThe supplied passphrase was wrong or not given")];
790	      aBOOL = NO;
791	      RELEASE(aString);
792	      break;
793	    }
794	  else if ([aString hasPrefix: @"DECRYPTION_FAILED"])
795	    {
796	      [theMessage appendString: _(@"\nWrong passphrase (or something else)")];
797	      aBOOL = NO;
798		  RELEASE(aString);
799	      break;
800	    }
801	  else if ([aString hasPrefix: @"NODATA"])
802	    {
803	      [theMessage appendString: _(@"\nNo data has been found")];
804	      aBOOL = NO;
805		  RELEASE(aString);
806	      break;
807	    }
808	  else if ([aString hasPrefix: @"SIGEXPIRED"] ||  [aString hasPrefix: @"KEYEXPIRED"])
809	    {
810	      [theMessage appendString: _(@"\nYour key is expired. You must generate a new one\nbefore trying to send again any signed or encrypted messages.")];
811	      aBOOL = NO;
812		  RELEASE(aString);
813	      break;
814	    }
815	  // We check errors for the signature
816	  else if ([aString hasPrefix: @"BADSIG"])
817	    {
818	      [theMessage appendString:_(@"\nThe signature has NOT been VERIFIED okay.")];
819	      aBOOL = NO;
820		  RELEASE(aString);
821	      break;
822		}
823	  else if ([aString hasPrefix: @"ERRSIG"])
824	    {
825	      [theMessage appendString:_(@"\nIt was NOT possible to CHECK the signature.\nThis may be caused by a missing public key or an unsupported algorithm.")];
826	      aBOOL = NO;
827          RELEASE(aString);
828	      break;
829	    }
830	  RELEASE(aString);
831	} // if ( [[allLines objectAtIndex: i] hasCPrefix: "[GNUPG:] "] )
832      else
833	{
834	  NSArray *aLanguagesArray;
835
836	  aLanguagesArray = [[NSUserDefaults standardUserDefaults] stringArrayForKey: @"AppleLanguages"];
837
838	  // We check the user's preferred language.
839	  // FIXME: Use the right encoding depending of the user's preferred language
840	  //        or simply use UTF8?
841	  if ([(NSString *)[aLanguagesArray objectAtIndex: 0] isEqualToString: @"Japanese"])
842	    {
843	      NSString *aString;
844
845	      aString = [[NSString alloc] initWithData: [allLines objectAtIndex: i]
846					  encoding: NSJapaneseEUCStringEncoding];
847	      [theMessage appendFormat: @"\n%@", aString];
848	      RELEASE(aString);
849            }
850	  else
851	    {
852	      [theMessage appendFormat: @"\n%@", [[allLines objectAtIndex: i] asciiString]];
853            }
854	}
855    }
856
857  return aBOOL;
858}
859
860
861//
862// GPG commands:
863//
864//  --batch  Use batch mode.  Never ask, do not allow  inter�
865//           active commands.
866//
867//  --status-fd n
868//           Write   special   status  strings  to  the  file
869//           descriptor n.  See the file DETAILS in the docu�
870//           mentation for a listing of them.
871//
872//  --passphrase-fd n
873//           Read the passphrase from file descriptor  n.  If
874//           you  use  0  for  n, the passphrase will be read
875//           from stdin.     This can only be  used  if  only
876//           one  passphrase  is  supplied.   Don't  use this
877//           option if you can avoid it.
878//
879- (void) _decryptPart: (CWPart *) thePart
880	    multipart: (BOOL) aBOOL
881	      message: (CWMessage *) theMessage
882
883{
884  NSString *aLaunchPath, *inFilename, *outFilename, *aPassphrase, *aUserID;
885  NSPipe *standardInput, *standardError;
886  NSMutableString *aWarningMessage;
887  NSMutableData *aMutableData;
888  NSMutableArray *arguments;
889  NSTask *aTask;
890
891  BOOL is_signed_only;
892  char *s1, *s2;
893
894  // We generate temporary filenames
895  s1 = tempnam([GNUMailTemporaryDirectory() cString], NULL);
896  inFilename = [NSString stringWithCString: s1];
897
898  s2 = tempnam([GNUMailTemporaryDirectory() cString], NULL);
899  outFilename = [NSString stringWithFormat: @"%s.out", s2];
900
901  //
902  // We get our User ID (E-Mail address only). We use it to obtain the GPG passphrase:
903  //
904  if (![[NSUserDefaults standardUserDefaults] objectForKey: @"PGPBUNDLE_USE_FROM_FOR_SIGNING"] ||
905      [[NSUserDefaults standardUserDefaults] integerForKey: @"PGPBUNDLE_USE_FROM_FOR_SIGNING"] == NSOnState)
906    {
907      aUserID = [[theMessage from] address];
908    }
909  else
910    {
911      aUserID = [[NSUserDefaults standardUserDefaults] stringForKey: @"PGPBUNDLE_USER_EMAIL_ADDRESS"];
912    }
913
914
915  // We set the right property, depending on the content's prefix
916  if ([(NSData *)[thePart content] hasCPrefix: "-----BEGIN PGP MESSAGE-----"])
917    {
918      [theMessage setProperty: [NSNumber numberWithInt: ENCRYPTED]  forKey: @"CONTENT-STATUS"];
919      is_signed_only = NO;
920    }
921  else
922    {
923      [theMessage setProperty: [NSNumber numberWithInt: SIGNED]  forKey: @"CONTENT-STATUS"];
924      is_signed_only = YES;
925    }
926
927
928  // We first get our launch path
929  aLaunchPath = [[NSUserDefaults standardUserDefaults] stringForKey: @"PGPBUNDLE_GPG_PATH"];
930
931  if (!aLaunchPath || [aLaunchPath length] == 0)
932    {
933#ifdef MACOSX
934      aLaunchPath = @"/usr/local/bin/gpg";
935#else
936      aLaunchPath = @"/usr/bin/gpg";
937#endif
938    }
939
940  // We now verify if our launch path exists and is executable
941  if (![[NSFileManager defaultManager] isExecutableFileAtPath: aLaunchPath])
942    {
943      NSRunAlertPanel(_(@"Error!"),
944		      _(@"The file %@ does not exist or is not executable"),
945                      _(@"OK"),   // default
946                      NULL,       // alternate
947                      NULL,
948		      aLaunchPath);
949      return;
950    }
951
952  // We create our task object
953  aTask = [[NSTask alloc] init];
954  [aTask setLaunchPath: aLaunchPath];
955
956  //
957  // We initialize our basic arguments
958  //
959  arguments = [[NSMutableArray alloc] initWithObjects:  @"--batch",
960				      @"--no-tty",
961				      @"--status-fd",
962				      @"2",
963				      @"-o",
964				      outFilename,
965				      nil];
966
967  // We write our mail content to a temporary file
968  [(NSData *)[thePart content] writeToFile: inFilename  atomically: YES];
969  [[NSFileManager defaultManager] enforceMode: 0600  atPath: inFilename];
970
971  if (!is_signed_only)
972    {
973      [arguments addObject: @"--passphrase-fd"];
974      [arguments addObject: @"0"];
975
976      // We create our standard input handle pipe
977      standardInput = [NSPipe pipe];
978
979      // We write our passphrase on the pipe
980      aPassphrase = [self _passphraseForID: aUserID];  // ex: ludovic@Sophos.ca
981      [[standardInput fileHandleForWriting] writeData:
982					      [aPassphrase dataUsingEncoding:
983							     NSASCIIStringEncoding]];
984      [[standardInput fileHandleForWriting] closeFile];
985
986      // We set the stdin / stdout
987      [aTask setStandardInput: standardInput];
988    }
989
990  // We add the extra arguments, to decrypt the filename instead of reading
991  // everything on stdin
992  [arguments addObject: @"--decrypt"];
993  [arguments addObject: inFilename];
994
995  // We set our task's standard error
996  standardError = [NSPipe pipe];
997  [aTask setStandardError: standardError];
998
999  // We set our task's arguments
1000  [aTask setArguments: arguments];
1001  RELEASE(arguments);
1002
1003  // We create our mutable data and string object
1004  aWarningMessage = [[NSMutableString alloc]
1005		      initWithString: _(@"Decryption failed due the following reason(s):\n")];
1006  aMutableData = [[NSMutableData alloc] init];
1007
1008  // We lauch our task
1009  [aTask launch];
1010
1011  // While the task is dunning, we accumulate the infoz read on stderr
1012  // into aMutableData.
1013  while ([aTask isRunning])
1014    {
1015      [aMutableData appendData: [[standardError fileHandleForReading] availableData]];
1016    }
1017
1018
1019  // We analyse the output of our GPG task to stderr. If it the decryption
1020  // failed, we do some cleanups.
1021  if (![self _analyseTaskOutput: aMutableData  message: aWarningMessage])
1022    {
1023      // We show the reason why decryption failed
1024      NSRunAlertPanel(_(@"Error!"),
1025		      aWarningMessage,
1026                      _(@"OK"),   // default
1027                      NULL,       // alternate
1028                      NULL);
1029
1030
1031      // FIXME: move to the _analyseTaskOutput:: method
1032      // We remove the passphrase for this ID
1033      [passphraseCache removeObjectForKey: aUserID];
1034
1035      // We remove our temporary files and we free some vars
1036      [[NSFileManager defaultManager] removeFileAtPath: inFilename  handler: nil];
1037      [[NSFileManager defaultManager] removeFileAtPath: outFilename  handler: nil];
1038      free(s1);
1039      free(s2);
1040
1041      RELEASE(aMutableData);
1042      RELEASE(aWarningMessage);
1043      RELEASE(aTask);
1044
1045      return;
1046    }
1047  else
1048    {
1049      // We check the signed message
1050      // We check the decrypted PGP-Combined-message whether it has a good signature or not.
1051      // We check the decrypted OpenPGP-Combined-message whether it has a good signature or not.
1052      if ([theMessage propertyForKey: @"CONTENT-STATUS"])
1053	{
1054	  NSRange aRange;
1055	  int status;
1056
1057	  aRange = [aMutableData rangeOfCString: "GOODSIG"  options: NSCaseInsensitiveSearch];
1058	  status = [[theMessage propertyForKey: @"CONTENT-STATUS"] intValue];
1059
1060	  if (status == ENCRYPTED)
1061	    {
1062	      if (aRange.length > 0)
1063		{
1064		  [theMessage setProperty: [NSNumber numberWithInt: SIGNED_AND_ENCRYPTED]  forKey: @"CONTENT-STATUS"];
1065		}
1066	    }
1067	  else if (status == SIGNED)
1068	    {
1069	      if (aRange.length == 0)
1070		{
1071		  [theMessage setProperty: [NSNumber numberWithInt: NOT_SIGNED]  forKey: @"CONTENT-STATUS"];
1072		}
1073	    }
1074	}
1075    }
1076
1077  RELEASE(aMutableData);
1078  RELEASE(aWarningMessage);
1079
1080  //
1081  // Decryption is done. We now set the new content of the message
1082  // by replacing the actual one.
1083  //
1084  if (!aBOOL)
1085    {
1086      [thePart setContent: [NSData dataWithContentsOfFile: outFilename]];
1087    }
1088  else
1089    {
1090      NSMutableData *aMutableData;
1091      NSData *aData;
1092      NSRange aRange, sRange, eRange;
1093      BOOL noHeader;
1094
1095      // We replace all occurences of \r\n by \n
1096      aMutableData = [[NSMutableData alloc] initWithData: [NSData dataWithContentsOfFile: outFilename]];
1097      [aMutableData replaceCRLFWithLF];
1098
1099      // We unfold all lines
1100      aData = [aMutableData unfoldLines];
1101      noHeader = NO;
1102
1103      //
1104      // We grab only the headers we are interested in to and we parse them.
1105      // We parse and set the Content-Transfer-Encoding
1106      //
1107      sRange = [aData rangeOfCString: "Content-Transfer-Encoding:"  options: NSCaseInsensitiveSearch];
1108
1109      if (sRange.length > 0)
1110	{
1111	  sRange.length = [aData length] - sRange.location;
1112	  eRange = [aData rangeOfCString: "\n"  options: 0  range: sRange];
1113	  aRange.location = sRange.location;
1114	  aRange.length = eRange.location - sRange.location;
1115	  [CWParser parseContentTransferEncoding: [aData subdataWithRange: aRange]  inPart: thePart];
1116	}
1117      else
1118	{
1119	  [thePart setContentTransferEncoding: PantomimeEncodingNone];
1120	}
1121
1122      //
1123      // We parse and set the Content-Type & boundary
1124      //
1125      sRange = [aData rangeOfCString:"Content-Type:"  options: NSCaseInsensitiveSearch];
1126
1127      if (sRange.length > 0)
1128	{
1129	  NSData *cData;
1130
1131	  sRange.length = [aData length] - sRange.location;
1132	  eRange = [aData rangeOfCString: "\n"  options: 0  range: sRange];
1133	  aRange.location = sRange.location;
1134	  aRange.length = eRange.location - sRange.location + 1;
1135	  [CWParser parseContentType: [aData subdataWithRange: aRange]  inPart: thePart];
1136
1137	  // We set the protocol
1138	  eRange = [aData rangeOfCString: "application/pgp-signature"  options: NSCaseInsensitiveSearch];
1139
1140	  if (eRange.length > 0)
1141	    {
1142	      [thePart setProtocol: [@"application/pgp-signature"
1143				      dataUsingEncoding: NSASCIIStringEncoding]];
1144	    }
1145
1146	  // We parse the boundary and remove the quotation-mark in it
1147	  cData = [thePart boundary];
1148	  eRange = [cData rangeOfCString: "\""  options: NSCaseInsensitiveSearch];
1149
1150	  if ((eRange.location == 0) && (eRange.length > 0))
1151	    {
1152	      cData = [cData subdataFromIndex: 1];
1153	      eRange = [cData rangeOfCString: "\""  options: NSCaseInsensitiveSearch];
1154
1155	      if ( eRange.length > 0 )
1156		{
1157		  [thePart setBoundary: [cData subdataToIndex: eRange.location]];
1158		}
1159	    }
1160	}
1161      else
1162	{
1163	  [thePart setContentType: @"text/plain"];
1164	  noHeader = YES;
1165	}
1166
1167      // We set the raw content
1168      aRange = [aData rangeOfCString: "\n\n"];	// The body is separated from the header by an empty line
1169
1170      if ((aRange.length > 0) && !noHeader)
1171	{
1172	  if ([[thePart boundary] length] > 0)
1173	    {
1174	      aRange = [aData rangeOfCString: [[NSString stringWithFormat: @"--%s", [[thePart boundary] cString]] cString]];
1175	    }
1176	  else
1177	    {
1178	      aRange.location += 2;
1179	    }
1180	}
1181      else
1182	{
1183	  aRange.location = 0;
1184	}
1185
1186      [CWMIMEUtility setContentFromRawSource: [aData subdataFromIndex: aRange.location]
1187		     inPart: thePart];
1188
1189      //
1190      // We must check encapsulated-signed-message.
1191      //
1192      if ([thePart isMIMEType: @"multipart"  subType: @"signed"])
1193	{
1194	  CWMIMEMultipart *aMimeMultipart;
1195	  int i, c;
1196
1197	  aMimeMultipart = (CWMIMEMultipart *)[thePart content];
1198	  c = [aMimeMultipart count];
1199
1200	  for (i = 1; i < c; i++)
1201	    {
1202	      CWPart *aPart;
1203
1204	      aPart = [aMimeMultipart partAtIndex:i];
1205
1206	      if ([aPart isMIMEType: @"application"  subType: @"pgp-signature"])
1207		{
1208		  [self _verifyPart: [aMimeMultipart partAtIndex: 0]
1209			allPart: thePart
1210			rawSource: (NSData *)aMutableData
1211			signaturePart: aPart
1212			message: theMessage];
1213		  [aMimeMultipart removePart: aPart];
1214		  break;
1215		}
1216	    }
1217	}
1218
1219      RELEASE(aMutableData);
1220    }
1221
1222
1223
1224  // Cleanups
1225  [[NSFileManager defaultManager] removeFileAtPath: inFilename  handler: nil];
1226  [[NSFileManager defaultManager] removeFileAtPath: outFilename handler: nil];
1227  free(s1);
1228  free(s2);
1229
1230  RELEASE(aTask);
1231}
1232
1233
1234
1235
1236//
1237// GPG commands:
1238//
1239//  --batch  Use batch mode.  Never ask, do not allow  inter�
1240//           active commands.
1241//
1242//  --status-fd n
1243//           Write   special   status  strings  to  the  file
1244//           descriptor n.  See the file DETAILS in the docu�
1245//           mentation for a listing of them.
1246//
1247- (void) _verifyPart: (CWPart *) thePart
1248             allPart: (CWPart *) allPart
1249           rawSource: (NSData *) rawData
1250       signaturePart: (CWPart *) signPart
1251             message: (CWMessage *) theMessage
1252{
1253  NSString *aLaunchPath, *outFilename, *signFilename, *dataFilename, *aBoundary;
1254  NSPipe *standardInput, *standardError;
1255  NSMutableString *aWarningMessage;
1256  NSMutableData *aMutableData;
1257  NSMutableArray *arguments;
1258  NSTask *aTask;
1259  NSData *aData;
1260
1261  NSRange aRange;
1262  char *s1, *s2;
1263
1264  // We generate tempory filenames
1265  s1 = tempnam([GNUMailTemporaryDirectory() cString], NULL);
1266  dataFilename = [NSString stringWithFormat: @"%s", s1];
1267  signFilename = [NSString stringWithFormat: @"%s.sig", s1];
1268  s2 = tempnam([GNUMailTemporaryDirectory() cString], NULL);
1269  outFilename = [NSString stringWithFormat: @"%s.out", s2];
1270
1271  // We first get our launch path
1272  aLaunchPath = [[NSUserDefaults standardUserDefaults] stringForKey: @"PGPBUNDLE_GPG_PATH"];
1273
1274  if (!aLaunchPath || ([aLaunchPath length] == 0))
1275    {
1276#ifdef MACOSX
1277      aLaunchPath = @"/usr/local/bin/gpg";
1278#else
1279      aLaunchPath = @"/usr/bin/gpg";
1280#endif
1281    }
1282
1283  // We now verify if file exist & is executable
1284  if (![[NSFileManager defaultManager] isExecutableFileAtPath: aLaunchPath])
1285    {
1286      NSRunAlertPanel(_(@"Error!"),
1287		      _(@"The file %@ does not exist or is not executable"),
1288                      _(@"OK"),   // default
1289                      NULL,       // alternate
1290                      NULL,
1291		      aLaunchPath);
1292      return;
1293    }
1294
1295  // We create our task object
1296  aTask = [[NSTask alloc] init];
1297  [aTask setLaunchPath: aLaunchPath];
1298
1299  // We initialize our arguments
1300  arguments = [[NSMutableArray alloc] initWithObjects:  @"--batch", // Use batch mode.
1301				      @"--no-tty", 		    // Make sure that the TTY (terminal) is
1302				                                    // never used for any output.
1303				      @"--status-fd", @"2",	    // Write special status strings to the file
1304				                                    // descriptor 2.
1305				      @"-o", outFilename,	    // Write output to file.
1306				      @"--verify",
1307				      signFilename, 		    // signature file name
1308				      dataFilename, 		    // signed file name
1309				      nil];
1310
1311  // We write our signature to a temporary file
1312  [(NSData *)[signPart content] writeToFile: signFilename  atomically: YES];
1313  [[NSFileManager defaultManager] enforceMode: 0600  atPath: signFilename];
1314
1315  // We create our standard input handle pipe
1316  standardInput = [NSPipe pipe];
1317
1318  // We the raw source of our part / message isn't available, let's obtain it.
1319  if (rawData == nil)
1320    {
1321      // We replace all occurences of \r\n by \n
1322      aMutableData = AUTORELEASE([[NSMutableData alloc] initWithData: [theMessage rawSource]]);
1323      [aMutableData replaceCRLFWithLF];
1324      aData = aMutableData;
1325      aBoundary = [NSString stringWithFormat: @"--%s", [[theMessage boundary] cString]];
1326    }
1327  else
1328    {
1329      aData = rawData;
1330      aBoundary = [NSString stringWithFormat: @"--%s", [[allPart boundary] cString]];
1331    }
1332
1333  // We get the data enclosed in our boundary. First we search for
1334  // a starting point.
1335  aRange = [aData rangeOfCString: [aBoundary cString]];
1336  aData = [aData subdataFromIndex: NSMaxRange(aRange)+1];
1337
1338  // Then we search for an ending point, trimming everything
1339  // to that ending point.
1340  aRange = [aData rangeOfCString: [aBoundary cString]];
1341  aRange.length = aRange.location-1;
1342  aRange.location = 0;
1343  aData = [aData subdataWithRange: aRange];
1344
1345  // Before feeding everything to gpg, we must replace
1346  // all occurences of LF by CRLF.
1347  aMutableData = AUTORELEASE([[NSMutableData alloc] initWithData: aData]);
1348  aData = (NSData *)[aMutableData replaceLFWithCRLF];
1349
1350  // We finally write our data to a file. We'll use this
1351  // file for verification.
1352  [aData writeToFile: dataFilename  atomically: YES];
1353  [[NSFileManager defaultManager] enforceMode: 0600  atPath: dataFilename];
1354
1355
1356  // We set the stdin / stdout
1357  [aTask setStandardInput: standardInput];
1358
1359  // We set our task's standard error
1360  standardError = [NSPipe pipe];
1361  [aTask setStandardError: standardError];
1362
1363  // We set our task's arguments
1364  [aTask setArguments: arguments];
1365  RELEASE(arguments);
1366
1367  // We create our mutable data and string object
1368  aWarningMessage = [[NSMutableString alloc]
1369		      initWithString: _(@"Authentication failed due the following reason(s):\n")];
1370  aMutableData = [[NSMutableData alloc] init];
1371
1372  // We lauch our task
1373  [aTask launch];
1374
1375  // While the task is dunning, we accumulate the infoz read on stderr
1376  // into aMutableData.
1377  while ([aTask isRunning])
1378    {
1379      [aMutableData appendData: [[standardError fileHandleForReading] availableData]];
1380    }
1381
1382  // We analyse the output of our GPG task to stderr. If it the authentication
1383  // failed, we do some cleanups.
1384  if (![self _analyseTaskOutput: aMutableData  message: aWarningMessage])
1385    {
1386      // We show the reason why authentication failed
1387      NSRunAlertPanel(_(@"Error!"),
1388		      aWarningMessage,
1389		      _(@"OK"),   // default
1390		      NULL,       // alternate
1391		      NULL);
1392
1393      // We remove our temporary files and we free some vars
1394      [[NSFileManager defaultManager] removeFileAtPath: dataFilename  handler:nil];
1395      [[NSFileManager defaultManager] removeFileAtPath: signFilename  handler:nil];
1396      [[NSFileManager defaultManager] removeFileAtPath: outFilename  handler:nil];
1397      free(s1);
1398      free(s2);
1399
1400      RELEASE(aMutableData);
1401      RELEASE(aWarningMessage);
1402      RELEASE(aTask);
1403
1404      return;
1405    }
1406  else
1407    {
1408      if ([aMutableData rangeOfCString: "GOODSIG"  options: NSCaseInsensitiveSearch].length > 0)
1409	{
1410	  [theMessage setProperty: [NSNumber numberWithInt: SIGNED]  forKey: @"CONTENT-STATUS"];
1411	}
1412    }
1413
1414
1415  // Cleanups
1416  [[NSFileManager defaultManager] removeFileAtPath: dataFilename  handler:nil];
1417  [[NSFileManager defaultManager] removeFileAtPath: signFilename  handler:nil];
1418  [[NSFileManager defaultManager] removeFileAtPath: outFilename  handler:nil];
1419  free(s1);
1420  free(s2);
1421
1422  RELEASE(aMutableData);
1423  RELEASE(aWarningMessage);
1424  RELEASE(aTask);
1425}
1426
1427
1428//
1429//
1430// GPG commands:
1431//
1432// SIGN ONLY to ludovic@Sophos.ca: OR
1433// SIGN ONLY to foo@bar.com
1434//
1435// /usr/bin/gpg --batch --no-tty --status-fd 2
1436// --comment 'Using GnuPG with Mozilla - http://enigmail.mozdev.org'
1437// --always-trust --encrypt-to ludovic@Sophos.ca --clearsign -u ludovic@Sophos.ca --passphrase-fd 0
1438//
1439//
1440// ENCRYPT ONLY to ludovic@Sophos.ca
1441//
1442// To encrypt, we must have the public key of -r
1443//
1444//
1445// /usr/bin/gpg --batch --no-tty --status-fd 2
1446// --comment 'Using GnuPG with Mozilla - http://enigmail.mozdev.org'
1447// --always-trust --encrypt-to ludovic@Sophos.ca -a -e -u ludovic@Sophos.ca -r ludovic@Sophos.ca
1448//
1449//
1450// ENCRYPT ONLY to foo@bar.com
1451//
1452// /usr/bin/gpg --batch --no-tty --status-fd 2
1453// --comment 'Using GnuPG with Mozilla - http://enigmail.mozdev.org'
1454// --always-trust --encrypt-to ludovic@Sophos.ca -a -e -u ludovic@Sophos.ca -r foo@bar.com
1455//
1456//
1457//  -a, --armor
1458//               Create ASCII armored output.
1459//
1460//  -e, --encrypt
1461//               Encrypt data. This option may be  combined  with
1462//               --sign.
1463//
1464//
1465// ENCRYPT AND SIGN to ludovic@Sophos.ca
1466//
1467// /usr/bin/gpg --batch --no-tty --status-fd 2
1468// --comment 'Using GnuPG with Mozilla - http://enigmail.mozdev.org'
1469// --always-trust --encrypt-to ludovic@Sophos.ca -a -e -s -u ludovic@Sophos.ca -r ludovic@Sophos.ca
1470// --passphrase-fd 0
1471//
1472// FIXME: Should return the new message instead of modifying directly :)
1473//
1474- (CWMessage *) _encryptMessage: (CWMessage *) theMessage
1475		      multipart: (BOOL) aBOOL
1476
1477{
1478  NSString *aLaunchPath, *aUserID, *aRecipientUserID, *inFilename, *outFilename, *aPassphrase;
1479  NSPipe *standardInput, *standardError;
1480  NSMutableString *aWarningMessage;
1481  NSMutableData *aMutableData;
1482  NSMutableArray *arguments;
1483  NSArray *allRecipients;
1484  NSTask *aTask;
1485
1486  BOOL encapsulationFlag;
1487  char *s1, *s2;
1488  int i;
1489
1490  encapsulationFlag = NO;
1491
1492  // We generate our filename
1493  s1 = tempnam([GNUMailTemporaryDirectory() cString], NULL);
1494  outFilename = [NSString stringWithCString: s1];
1495  s2 = tempnam([GNUMailTemporaryDirectory() cString], NULL);
1496  inFilename = [NSString stringWithCString: s2];
1497
1498
1499  // We get our User ID (E-Mail address only). We use it for the following GPG parameters
1500  // (and also to obtain the GPG passphrase):
1501  //
1502  // --encrypt-to
1503  // -u
1504  //
1505  if (![[NSUserDefaults standardUserDefaults] objectForKey: @"PGPBUNDLE_USE_FROM_FOR_SIGNING"] ||
1506      [[NSUserDefaults standardUserDefaults] integerForKey: @"PGPBUNDLE_USE_FROM_FOR_SIGNING"] == NSOnState)
1507    {
1508      aUserID = [[theMessage from] address];
1509    }
1510  else
1511    {
1512      aUserID = [[NSUserDefaults standardUserDefaults] stringForKey: @"PGPBUNDLE_USER_EMAIL_ADDRESS"];
1513    }
1514
1515  // We get our recipient User ID
1516  // FIXME: Support more than 1 recipient
1517  allRecipients = [theMessage recipients];
1518  aRecipientUserID = nil;
1519
1520  for (i = 0; i < [allRecipients count]; i++)
1521    {
1522      CWInternetAddress *aInternetAddress;
1523
1524      aInternetAddress = [allRecipients objectAtIndex: i];
1525
1526      if ([aInternetAddress type] == PantomimeToRecipient)
1527	{
1528	  aRecipientUserID = [aInternetAddress address];
1529	  break;
1530	}
1531    }
1532
1533
1534  // We first get our launch path
1535  aLaunchPath = [[NSUserDefaults standardUserDefaults] stringForKey: @"PGPBUNDLE_GPG_PATH"];
1536
1537  if (!aLaunchPath || [aLaunchPath length] == 0)
1538    {
1539#ifdef MACOSX
1540      aLaunchPath = @"/usr/local/bin/gpg";
1541#else
1542      aLaunchPath = @"/usr/bin/gpg";
1543#endif
1544    }
1545
1546  // We now verify if file exist & is executable
1547  if (![[NSFileManager defaultManager] isExecutableFileAtPath: aLaunchPath])
1548    {
1549      NSRunAlertPanel(_(@"Error!"),
1550		      _(@"The file %@ does not exist or is not executable"),
1551                      _(@"OK"),   // default
1552                      NULL,       // alternate
1553                      NULL,
1554		      aLaunchPath);
1555      return nil;
1556    }
1557
1558  // We create our task object
1559  aTask = [[NSTask alloc] init];
1560  [aTask setLaunchPath: aLaunchPath];
1561
1562  // Let's create our array of arguments
1563  arguments = [[NSMutableArray alloc] initWithObjects: @"--batch",
1564				      @"--no-tty",
1565				      @"--status-fd",
1566				      @"2",
1567				      @"--comment",
1568				      @"Using the GPG bundle for GNUMail",
1569				      @"--always-trust",
1570				      @"--encrypt-to",
1571				      aUserID,         // ex: ludovic@Sophos.ca
1572				      nil];
1573
1574  //
1575  // If we sign ONLY
1576  //
1577  if ([sign tag] == SIGNED && [encrypt tag] == NOT_ENCRYPTED)
1578    {
1579      if (aBOOL)
1580	{
1581	  [arguments addObject: @"-a"];	// ASCII armored output
1582	  [arguments addObject: @"-s"];	// make a signature
1583	  [arguments addObject: @"-b"];	// make a detached signature
1584	}
1585      else
1586	{
1587	  [arguments addObject: @"--clearsign"]; // make a clear text signature
1588	}
1589    }
1590  //
1591  // If we encrypt ONLY
1592  //
1593  else if ([sign tag] == NOT_SIGNED && [encrypt tag] == ENCRYPTED )
1594    {
1595      [arguments addObject: @"-a"];  // ASCII armored output
1596      [arguments addObject: @"-e"];  // encrypt
1597      [arguments addObject: @"-r"];  // recipient user-id
1598      [arguments addObject: aRecipientUserID];
1599    }
1600  //
1601  // If we BOTH sign AND encrypt
1602  //
1603  else if ([sign tag] == SIGNED && [encrypt tag] == ENCRYPTED)
1604    {
1605    if (aBOOL)
1606      {
1607        encapsulationFlag = YES;
1608        [arguments addObject: @"-a"];  // ASCII armored output
1609        [arguments addObject: @"-s"];  // make a signature
1610        [arguments addObject: @"-b"];  // make a detached signature
1611      }
1612    else
1613      {
1614	[arguments addObject: @"-a"];  // ASCII armored output
1615	[arguments addObject: @"-e"];  // encrypt
1616	[arguments addObject: @"-s"];  // and sign
1617	[arguments addObject: @"-r"];  // recipient user-id
1618	[arguments addObject: aRecipientUserID];
1619      }
1620    }
1621
1622  //
1623  // Last standard arguments
1624  //
1625  [arguments addObject: @"-u"];
1626  [arguments addObject: aUserID]; // ex: ludovic@Sophos.ca
1627  [arguments addObject: @"--passphrase-fd"];
1628  [arguments addObject: @"0"];
1629  [arguments addObject: @"-o"];
1630  [arguments addObject: outFilename];
1631
1632  if (aBOOL)
1633    {
1634      [arguments addObject: inFilename];
1635    }
1636
1637  [aTask setArguments: arguments];
1638  RELEASE(arguments);
1639
1640  if (aBOOL)
1641    {
1642      // We generate our raw data of the "Part"'s part of our Message
1643      NSMutableData *rawSourceOfPart;
1644      NSData *aData;
1645      NSRange aRange;
1646
1647      rawSourceOfPart = [[NSMutableData alloc] init];
1648
1649      // We add our Content-Type: abc/def; boundary="--foo" to our message
1650      if ([theMessage isMIMEType: @"multipart"  subType: @"signed"] &&
1651	  [encrypt tag] == ENCRYPTED &&
1652	  [sign tag] == NOT_SIGNED)
1653	{
1654	  [rawSourceOfPart appendCFormat: @"Content-Type: multipart/signed; protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\n\tboundary=\"%s\"\n\n",
1655			   [[theMessage boundary] cString]];
1656
1657	  // We append our message content
1658	  aData = [theMessage dataValue];
1659	  aRange = [aData rangeOfCString: "\n\n"];
1660	  aData = [aData subdataFromIndex: (aRange.location + 2)];
1661
1662	  [rawSourceOfPart appendData: aData];
1663
1664	  // We write our mail content to a temporary file
1665	  [[rawSourceOfPart replaceLFWithCRLF] writeToFile: inFilename  atomically: YES];
1666	  [[NSFileManager defaultManager] enforceMode: 0600  atPath: inFilename];
1667	}
1668      else
1669	{
1670	  [rawSourceOfPart appendCFormat: @"Content-Type: %@;\n", [theMessage contentType]];
1671	  [rawSourceOfPart appendCFormat: @"\tboundary=\""];
1672	  [rawSourceOfPart appendData: [theMessage boundary]];
1673	  [rawSourceOfPart appendCFormat: @"\"\n\n"];
1674
1675	  // We append our message content
1676	  aData = [theMessage dataValue];
1677	  aRange = [aData rangeOfCString: "\n\n"];
1678	  aData = [aData subdataFromIndex: (aRange.location + 2)];
1679
1680	  [rawSourceOfPart appendData: aData];
1681
1682	  // We write our mail content to a temporary file
1683	  if ([sign tag] == SIGNED)
1684	    {
1685	      [[rawSourceOfPart replaceLFWithCRLF] writeToFile: inFilename  atomically: YES];
1686	      [[NSFileManager defaultManager] enforceMode: 0600  atPath: inFilename];
1687	    }
1688	  else
1689	    {
1690	      [rawSourceOfPart writeToFile: inFilename  atomically: YES];
1691	      [[NSFileManager defaultManager] enforceMode: 0600  atPath: inFilename];
1692	    }
1693	}
1694      RELEASE(rawSourceOfPart);
1695    }
1696
1697  // We create our standard input handle pipe and we set the stdin
1698  standardInput = [NSPipe pipe];
1699  [aTask setStandardInput: standardInput];
1700
1701  // We set our task's standard error
1702  standardError = [NSPipe pipe];
1703  [aTask setStandardError: standardError];
1704
1705  // We obtain our passphrase
1706  aPassphrase = [self _passphraseForID: aUserID];    // ex: ludovic@Sophos.ca
1707
1708  // We lauch our task
1709  [aTask launch];
1710
1711  // We write everything to the pipe
1712  [[standardInput fileHandleForWriting] writeData:
1713					  [aPassphrase dataUsingEncoding:
1714							 NSASCIIStringEncoding]];
1715  // We 'flush' the passphrase by writing a \n
1716  [[standardInput fileHandleForWriting] writeData: [NSData dataWithBytes: "\n"  length: 1]];
1717
1718  // We write our part content
1719  if (!aBOOL)
1720    {
1721      NSData *aData;
1722      NSRange aRange;
1723      int encoding;
1724
1725      encoding = [NSString encodingForCharset:
1726			     [[theMessage charset] dataUsingEncoding: NSASCIIStringEncoding]];
1727      if (encoding == -1)
1728	{
1729	  encoding = NSASCIIStringEncoding;
1730	}
1731
1732      // We set our message content
1733      aData = [theMessage dataValue];
1734      aRange = [aData rangeOfCString: "\n\n"];
1735      aData = [aData subdataFromIndex: (aRange.location + 2)];
1736
1737      if ([theMessage contentTransferEncoding] == PantomimeEncodingQuotedPrintable)
1738	{
1739	  aData = [aData decodeQuotedPrintableInHeader: NO];
1740
1741	  if (encoding == NSISO2022JPStringEncoding)
1742	    {
1743	      // ISO-2022-JP message does not need quoted-printable encoding, because ISO-2022-JP is 7bit code
1744	      [theMessage setContentTransferEncoding: PantomimeEncodingNone];
1745	    }
1746	  else if (encoding == NSUTF8StringEncoding)
1747	    {
1748	      // If the encoding of Japanese messages is UTF-8, base64 is better than quoted-printable
1749	      [theMessage setContentTransferEncoding: PantomimeEncodingBase64];
1750	    }
1751	}
1752      else if ([theMessage contentTransferEncoding] == PantomimeEncodingBase64)
1753	{
1754	  aData = [aData decodeBase64];
1755	}
1756
1757      // We write the data to our pipe
1758      [[standardInput fileHandleForWriting] writeData: aData];
1759    }
1760
1761
1762  [[standardInput fileHandleForWriting] closeFile];
1763
1764  // We create our mutable data and string object
1765  aWarningMessage = [[NSMutableString alloc]
1766		      initWithString: _(@"Encryption failed due the following reason(s):\n")];
1767  aMutableData = [[NSMutableData alloc] init];
1768
1769
1770  // While the task is dunning, we accumulate the infoz read on stderr
1771  // into aMutableData.
1772  while ([aTask isRunning])
1773    {
1774      [aMutableData appendData: [[standardError fileHandleForReading] availableData]];
1775    }
1776
1777  // We analyse the output of our GPG task to stderr. If it the encryption
1778  // failed, we do some cleanups.
1779  if (![self _analyseTaskOutput: aMutableData  message: aWarningMessage] ||
1780      [aTask terminationStatus] > 0)
1781    {
1782      // We show the reason why decryption failed
1783      NSRunAlertPanel(_(@"Error!"),
1784		      aWarningMessage,
1785                      _(@"OK"),   // default
1786                      NULL,       // alternate
1787                      NULL);
1788
1789
1790      // FIXME: move to the _analyseTaskOutput:: method
1791      // We remove the passphrase for this ID
1792      [passphraseCache removeObjectForKey: aUserID];
1793
1794      // We remove our temporary file and we free some vars
1795      [[NSFileManager defaultManager] removeFileAtPath: outFilename
1796				      handler: nil];
1797
1798      if (aBOOL)
1799	{
1800	  [[NSFileManager defaultManager] removeFileAtPath: inFilename  handler: nil];
1801	}
1802
1803      free(s2);
1804      free(s1);
1805      RELEASE(aWarningMessage);
1806      RELEASE(aMutableData);
1807      RELEASE(aTask);
1808
1809      return nil;
1810    }
1811
1812  RELEASE(aWarningMessage);
1813  RELEASE(aMutableData);
1814
1815  //
1816  // Encryption or signing (or both!) has completed. We now replace the
1817  // actual content of the message with the new one.
1818  //
1819  if (!aBOOL)
1820    {
1821      [theMessage setContent: [NSData dataWithContentsOfFile: outFilename]];
1822    }
1823  else if (([sign tag] == SIGNED && [encrypt tag] == NOT_ENCRYPTED) || encapsulationFlag)
1824    {
1825      NSMutableData *rawSource, *aMutableData;
1826      NSString *aString, *aBoundary;
1827      NSData  *aBoundaryData, *aData;
1828      NSRange aRange;
1829
1830      // We generate a new boundary
1831      aBoundaryData = [CWMIMEUtility globallyUniqueBoundary];
1832      aBoundary = [NSString stringWithFormat: @"\n--%s", [aBoundaryData cString]];
1833
1834      aString = [NSString stringWithFormat: @"Content-type: multipart/signed; protocol=\"application/pgp-signature\"; micalg=pgp-sha1;\n\tboundary=\"%s\"\n", [aBoundaryData cString]];
1835      aString = [NSString stringWithFormat: @"%@%@\nContent-Type: multipart/mixed; boundary=\"%s\"\n",
1836			  aString, aBoundary, [[theMessage boundary] cString]];
1837
1838      rawSource = [[NSMutableData alloc] init];
1839      [rawSource appendCFormat: @"%@\n", aString];
1840
1841      // We replace all occurences of \r\n by \n
1842      aMutableData = [[NSMutableData alloc] initWithData: [NSData dataWithContentsOfFile: inFilename]];
1843      [aMutableData replaceCRLFWithLF];
1844      aData = aMutableData;
1845      aRange = [aData rangeOfCString: "\n\n"];
1846      aData = [aData subdataFromIndex: (aRange.location + 2)];
1847      [rawSource appendData: aData];
1848      RELEASE(aMutableData);
1849
1850      // We set our signature
1851      [rawSource appendCFormat: @"%@\ncontent-type: application/pgp-signature\n\n%@%@--\n",
1852                    aBoundary, [NSString stringWithContentsOfFile: outFilename], aBoundary];
1853
1854      [theMessage setBoundary: aBoundaryData];
1855      [theMessage setContentType: @"multipart/signed"];
1856      [theMessage setProtocol: [@"application/pgp-signature"
1857                                    dataUsingEncoding: NSASCIIStringEncoding]];
1858      [theMessage setContentTransferEncoding: PantomimeEncodingNone];
1859      [CWMIMEUtility setContentFromRawSource: rawSource  inPart: (CWPart *)theMessage];
1860      RELEASE(rawSource);
1861
1862      if (encapsulationFlag)
1863	{
1864	  [sign setTag: NOT_SIGNED];
1865	  [self _encryptMessage: theMessage  multipart: YES];
1866	}
1867    }
1868  else
1869    {
1870      CWMIMEMultipart *aMimeMultipart;
1871      NSData *aBoundary;
1872      NSString *aString;
1873      CWPart *aPart;
1874
1875      // We create our new multipart object
1876      aMimeMultipart = [[CWMIMEMultipart alloc] init];
1877
1878      //
1879      // We add our extra part (Version: 1)
1880      //
1881      aPart = [[CWPart alloc] init];
1882      [aPart setContentTransferEncoding: PantomimeEncodingNone];
1883      [aPart setContentType: @"application/pgp-encrypted"];
1884      [aPart setContent: [@"Version: 1" dataUsingEncoding: NSASCIIStringEncoding]];
1885      [aPart setSize: 10];
1886      [aMimeMultipart addPart: aPart];
1887      RELEASE(aPart);
1888
1889      //
1890      // We add our encrypted part
1891      //
1892      // This is safe since our output is ASCII armored when encrypted
1893      aString = [NSString stringWithContentsOfFile: outFilename];
1894      aString = [aString stringByAppendingString: @"\n"];
1895
1896      aPart = [[CWPart alloc] init];
1897      [aPart setContentTransferEncoding: PantomimeEncodingNone];
1898      [aPart setContentType: @"application/octet-stream"];
1899      [aPart setContent: [aString dataUsingEncoding: NSASCIIStringEncoding]];
1900      [aPart setSize: [aString length]];
1901      [aMimeMultipart addPart: aPart];
1902      RELEASE(aPart);
1903
1904      // We generate a new boundary
1905      aBoundary = [CWMIMEUtility globallyUniqueBoundary];
1906
1907      // We set the new boundary, the new Content-Type and the Content-Transfer-Encoding to our message
1908      [theMessage setBoundary: aBoundary];
1909      [theMessage setContentType: @"multipart/encrypted"];
1910      [theMessage setProtocol: [@"application/pgp-encrypted"
1911				 dataUsingEncoding: NSASCIIStringEncoding]];
1912      [theMessage setContentTransferEncoding: PantomimeEncodingNone];
1913
1914      // We finally set the new multipart content
1915      [theMessage setContent: aMimeMultipart];
1916      RELEASE(aMimeMultipart);
1917    }
1918
1919  // Cleanups
1920  [[NSFileManager defaultManager] removeFileAtPath: outFilename  handler: nil];
1921
1922  if (aBOOL)
1923    {
1924      [[NSFileManager defaultManager] removeFileAtPath: inFilename  handler: nil];
1925    }
1926
1927  free(s2);
1928  free(s1);
1929
1930  RELEASE(aTask);
1931
1932  return theMessage;
1933}
1934
1935
1936//
1937//
1938//
1939- (void) _messageFetchCompleted: (NSNotification *) theNotification
1940{
1941  NSTextView *aTextView;
1942  CWMessage *aMessage;
1943
1944  aMessage = [[theNotification userInfo] objectForKey: @"Message"];
1945  aTextView = [aMessage propertyForKey: @"NSTextView"];
1946  RETAIN(aTextView);
1947
1948  // We flush the previous properties
1949  [aMessage setProperty: nil  forKey: @"NSTextView"];
1950  [aMessage setProperty: nil  forKey: @"Loading"];
1951
1952  if (aTextView && [aTextView window] && [[aTextView window] isVisible])
1953    {
1954      id aController;
1955
1956      aController = [aTextView delegate];
1957
1958      if ([aController selectedMessage] == aMessage)
1959	{
1960	  // We decode our message for real now since we have our raw source.
1961	  [self messageWillBeDisplayed: aMessage  inView: aTextView];
1962
1963	  // We display it!
1964	  [Utilities showMessage: aMessage  target: aTextView  showAllHeaders: NO];
1965	}
1966    }
1967
1968  RELEASE(aTextView);
1969}
1970
1971//
1972// The ID is, for example: ludovic@Sophos.ca
1973//
1974- (NSString *) _passphraseForID: (NSString *) theID
1975{
1976  Passphrase *aPassphrase;
1977
1978  // We first verify in our cache
1979  aPassphrase = [passphraseCache objectForKey: theID];
1980
1981  // If we must prompt for the password
1982  if (!aPassphrase)
1983    {
1984      PasswordPanelController *theController;
1985      int result;
1986
1987      theController = [[PasswordPanelController alloc] initWithWindowNibName: @"PasswordPanel"];
1988      [[theController window] setTitle: [NSString stringWithFormat: _(@"Passphrase for %@"),
1989						  theID]];
1990
1991      result = [NSApp runModalForWindow: [theController window]];
1992
1993      // If the user has entered a password...
1994      if (result == NSRunStoppedResponse)
1995	{
1996	  // Let's cache this password...
1997	  aPassphrase = [[Passphrase alloc] initWithValue: [theController password]];
1998	  [passphraseCache setObject: aPassphrase
1999			   forKey: theID];
2000	  RELEASE(aPassphrase);
2001	}
2002      else
2003	{
2004	  aPassphrase = nil;
2005	}
2006
2007      RELEASE(theController);
2008    }
2009
2010  return [aPassphrase value];
2011}
2012
2013
2014//
2015//
2016//
2017- (void) _tick
2018{
2019  NSEnumerator *theEnumerator;
2020  NSCalendarDate *date;
2021  NSString *aKey;
2022
2023  NSInteger minutes, value;
2024
2025  theEnumerator = [passphraseCache keyEnumerator];
2026
2027  value = [[NSUserDefaults standardUserDefaults] integerForKey: @"PGPBUNDLE_PASSPHRASE_EXPIRY_VALUE"];
2028  date = (NSCalendarDate *)[NSCalendarDate date];
2029
2030  while ((aKey = [theEnumerator nextObject]))
2031    {
2032      Passphrase *aPassphrase;
2033
2034      aPassphrase = [passphraseCache objectForKey: aKey];
2035
2036      [date years: NULL
2037	    months: NULL
2038	    days: NULL
2039	    hours: NULL
2040	    minutes: &minutes
2041	    seconds: NULL
2042	    sinceDate: (NSCalendarDate *)[aPassphrase date]];
2043
2044      // We must remove the passphrase from the cache
2045      if (minutes >= value)
2046	{
2047	  [passphraseCache removeObjectForKey: aKey];
2048	}
2049    }
2050}
2051@end
2052