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