1/* 2 Copyright (C) 2007-2021 Inverse inc. 3 4 This file is part of SOGo. 5 6 SOGo is free software; you can redistribute it and/or modify it under 7 the terms of the GNU Lesser General Public License as published by the 8 Free Software Foundation; either version 2, or (at your option) any 9 later version. 10 11 SOGo is distributed in the hope that it will be useful, but WITHOUT ANY 12 WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 14 License for more details. 15 16 You should have received a copy of the GNU Lesser General Public 17 License along with SOGo; see the file COPYING. If not, write to the 18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 19 02111-1307, USA. 20*/ 21 22#import <Foundation/NSAutoreleasePool.h> 23#import <Foundation/NSDictionary.h> 24#import <Foundation/NSURL.h> 25#import <Foundation/NSValue.h> 26 27#import <NGObjWeb/NSException+HTTP.h> 28#import <NGObjWeb/WOContext+SoObjects.h> 29#import <NGExtensions/NGBase64Coding.h> 30#import <NGExtensions/NSNull+misc.h> 31#import <NGExtensions/NSObject+Logs.h> 32#import <NGExtensions/NSString+misc.h> 33#import <NGImap4/NGImap4Connection.h> 34#import <NGImap4/NGImap4Client.h> 35#import <NGImap4/NSString+Imap4.h> 36 37#import <SOGo/NSArray+Utilities.h> 38#import <SOGo/NSString+Utilities.h> 39#import <SOGo/SOGoAuthenticator.h> 40#import <SOGo/SOGoDomainDefaults.h> 41#import <SOGo/SOGoSystemDefaults.h> 42#import <SOGo/SOGoUserSettings.h> 43#import <SOGo/SOGoUserManager.h> 44#import <SOGo/SOGoSieveManager.h> 45 46#import "SOGoDraftsFolder.h" 47#import "SOGoMailNamespace.h" 48#import "SOGoSentFolder.h" 49#import "SOGoTrashFolder.h" 50#import "SOGoJunkFolder.h" 51#import "SOGoUser+Mailer.h" 52 53#import "SOGoMailAccount.h" 54#import <Foundation/NSProcessInfo.h> 55 56 57#define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav" 58 59@implementation SOGoMailAccount 60 61static NSString *inboxFolderName = @"INBOX"; 62 63- (id) init 64{ 65 if ((self = [super init])) 66 { 67 NSString *sieveFolderEncoding = [[SOGoSystemDefaults sharedSystemDefaults] sieveFolderEncoding]; 68 inboxFolder = nil; 69 draftsFolder = nil; 70 sentFolder = nil; 71 trashFolder = nil; 72 junkFolder = nil; 73 imapAclStyle = undefined; 74 identities = nil; 75 otherUsersFolderName = nil; 76 sharedFoldersName = nil; 77 subscribedFolders = nil; 78 sieveFolderUTF8Encoding = [sieveFolderEncoding isEqualToString: @"UTF-8"]; 79 } 80 81 return self; 82} 83 84- (void) dealloc 85{ 86 [inboxFolder release]; 87 [draftsFolder release]; 88 [sentFolder release]; 89 [trashFolder release]; 90 [junkFolder release]; 91 [identities release]; 92 [otherUsersFolderName release]; 93 [sharedFoldersName release]; 94 [subscribedFolders release]; 95 [super dealloc]; 96} 97 98- (BOOL) isInDraftsFolder 99{ 100 return NO; 101} 102 103- (void) _appendNamespace: (NSArray *) namespace 104 toFolders: (NSMutableArray *) folders 105{ 106 NSString *newFolder; 107 NSDictionary *currentPart; 108 int count, max; 109 110 max = [namespace count]; 111 for (count = 0; count < max; count++) 112 { 113 currentPart = [namespace objectAtIndex: count]; 114 newFolder 115 = [[currentPart objectForKey: @"prefix"] substringFromIndex: 1]; 116 if ([newFolder length]) 117 [folders addObjectUniquely: newFolder]; 118 } 119} 120 121- (void) _appendNamespaces: (NSMutableArray *) folders 122{ 123 NSDictionary *namespaceDict; 124 NSArray *namespace; 125 NGImap4Client *client; 126 127 client = [[self imap4Connection] client]; 128 namespaceDict = [client namespace]; 129 130 namespace = [namespaceDict objectForKey: @"personal"]; 131 if (namespace) 132 [self _appendNamespace: namespace toFolders: folders]; 133 134 namespace = [namespaceDict objectForKey: @"other users"]; 135 if (namespace) 136 { 137 [self _appendNamespace: namespace toFolders: folders]; 138 ASSIGN(otherUsersFolderName, [folders lastObject]); 139 } 140 141 namespace = [namespaceDict objectForKey: @"shared"]; 142 if (namespace) 143 { 144 [self _appendNamespace: namespace toFolders: folders]; 145 ASSIGN(sharedFoldersName, [folders lastObject]); 146 } 147} 148 149- (NSArray *) _namespacesWithKey: (NSString *) nsKey 150{ 151 NSDictionary *namespaceDict; 152 NSArray *namespace; 153 NGImap4Client *client; 154 NSMutableArray *folders; 155 156 client = [[self imap4Connection] client]; 157 namespaceDict = [client namespace]; 158 namespace = [namespaceDict objectForKey: nsKey]; 159 if (namespace) 160 { 161 folders = [NSMutableArray array]; 162 [self _appendNamespace: namespace toFolders: folders]; 163 } 164 else 165 folders = nil; 166 167 return folders; 168} 169 170- (NSArray *) otherUsersFolderNamespaces 171{ 172 return [self _namespacesWithKey: @"other users"]; 173} 174 175- (NSArray *) sharedFolderNamespaces 176{ 177 return [self _namespacesWithKey: @"shared"]; 178} 179 180- (NSArray *) toManyRelationshipKeysWithNamespaces: (BOOL) withNSs 181{ 182 NSMutableArray *folders; 183 NSArray *imapFolders, *nss; 184 185 imapFolders = [[self imap4Connection] subfoldersForURL: [self imap4URL]]; 186 folders = [imapFolders mutableCopy]; 187 [folders autorelease]; 188 if (withNSs) 189 [self _appendNamespaces: folders]; 190 else 191 { /* some implementation insist on returning NSs in the list of 192 folders... */ 193 nss = [self otherUsersFolderNamespaces]; 194 if (nss) 195 [folders removeObjectsInArray: nss]; 196 nss = [self sharedFolderNamespaces]; 197 if (nss) 198 [folders removeObjectsInArray: nss]; 199 } 200 201 return [[folders resultsOfSelector: @selector (asCSSIdentifier)] 202 stringsWithFormat: @"folder%@"]; 203} 204 205- (NSArray *) toManyRelationshipKeys 206{ 207 return [self toManyRelationshipKeysWithNamespaces: YES]; 208} 209 210- (SOGoIMAPAclStyle) imapAclStyle 211{ 212 SOGoDomainDefaults *dd; 213 214 if (imapAclStyle == undefined) 215 { 216 dd = [[context activeUser] domainDefaults]; 217 if ([[dd imapAclStyle] isEqualToString: @"rfc2086"]) 218 imapAclStyle = rfc2086; 219 else 220 imapAclStyle = rfc4314; 221 } 222 223 return imapAclStyle; 224} 225 226/* see http://tools.ietf.org/id/draft-ietf-imapext-acl */ 227- (BOOL) imapAclConformsToIMAPExt 228{ 229 NGImap4Client *imapClient; 230 NSArray *capability; 231 int count, max; 232 BOOL conforms; 233 234 conforms = NO; 235 236 imapClient = [[self imap4Connection] client]; 237 capability = [[imapClient capability] objectForKey: @"capability"]; 238 max = [capability count]; 239 for (count = 0; !conforms && count < max; count++) 240 { 241 if ([[capability objectAtIndex: count] hasPrefix: @"acl2"]) 242 conforms = YES; 243 } 244 245 return conforms; 246} 247 248/* capabilities */ 249- (BOOL) hasCapability: (NSString *) capability 250{ 251 NGImap4Client *imapClient; 252 NSArray *capabilities; 253 254 imapClient = [[self imap4Connection] client]; 255 capabilities = [[imapClient capability] objectForKey: @"capability"]; 256 257 return [capabilities containsObject: capability]; 258} 259 260- (BOOL) supportsQuotas 261{ 262 return [self hasCapability: @"quota"]; 263} 264 265- (BOOL) supportsQResync 266{ 267 return [self hasCapability: @"qresync"]; 268} 269 270- (BOOL) supportsMove 271{ 272 return [self hasCapability: @"move"]; 273} 274 275- (id) getInboxQuota 276{ 277 SOGoMailFolder *inbox; 278 NGImap4Client *client; 279 NSString *inboxName; 280 SOGoDomainDefaults *dd; 281 id inboxQuota, infos; 282 float quota; 283 284 inboxQuota = nil; 285 if ([self supportsQuotas]) 286 { 287 dd = [[context activeUser] domainDefaults]; 288 quota = [dd softQuotaRatio]; 289 inbox = [self inboxFolderInContext: context]; 290 inboxName = [NSString stringWithFormat: @"/%@", [inbox relativeImap4Name]]; 291 client = [[inbox imap4Connection] client]; 292 infos = [[client getQuotaRoot: [inbox relativeImap4Name]] objectForKey: @"quotas"]; 293 inboxQuota = [infos objectForKey: inboxName]; 294 if (quota != 0 && inboxQuota != nil) 295 { 296 // A soft quota ratio is imposed for all users 297 if ([[inboxQuota allKeys] containsObject: @"maxQuota"]) 298 { 299 // Storage quota 300 quota = quota * [(NSNumber*)[inboxQuota objectForKey: @"maxQuota"] intValue]; 301 inboxQuota = [NSDictionary dictionaryWithObjectsAndKeys: 302 [NSNumber numberWithLong: (long)(quota+0.5)], @"maxQuota", 303 [NSNumber numberWithLong: [[inboxQuota objectForKey: @"usedSpace"] longLongValue]], @"usedSpace", 304 nil]; 305 } 306 else if ([[inboxQuota allKeys] containsObject: @"maxMessages"]) 307 { 308 // Messages quota 309 quota = quota * [(NSNumber*)[inboxQuota objectForKey: @"maxMessages"] intValue]; 310 inboxQuota = [NSDictionary dictionaryWithObjectsAndKeys: 311 [NSNumber numberWithLong: (long)(quota+0.5)], @"maxMessages", 312 [NSNumber numberWithLong: [[inboxQuota objectForKey: @"messagesCount"] longLongValue]], @"messagesCount", 313 nil]; 314 } 315 } 316 } 317 318 return inboxQuota; 319} 320 321- (NSException *) updateFiltersAndForceActivation: (BOOL) forceActivation 322{ 323 return [self updateFiltersWithUsername: nil 324 andPassword: nil 325 forceActivation: forceActivation]; 326} 327 328- (NSException *) updateFilters 329{ 330 return [self updateFiltersWithUsername: nil 331 andPassword: nil 332 forceActivation: NO]; 333} 334 335- (NSException *) updateFiltersWithUsername: (NSString *) theUsername 336 andPassword: (NSString *) thePassword 337 forceActivation: (BOOL) forceActivation 338{ 339 SOGoSieveManager *manager; 340 341 manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]]; 342 343 return [manager updateFiltersForAccount: self 344 withUsername: theUsername 345 andPassword: thePassword 346 forceActivation: forceActivation]; 347} 348 349 350/* hierarchy */ 351 352- (SOGoMailAccount *) mailAccountFolder 353{ 354 return self; 355} 356 357- (NSArray *) _allFoldersFromNS: (NSString *) namespace 358 subscribedOnly: (BOOL) subscribedOnly 359{ 360 NSArray *folders; 361 NSURL *nsURL; 362 NSString *baseURLString, *urlString; 363 364 baseURLString = [self imap4URLString]; 365 urlString = [NSString stringWithFormat: @"%@%@/", baseURLString, [namespace stringByEscapingURL]]; 366 nsURL = [NSURL URLWithString: urlString]; 367 folders = [[self imap4Connection] allFoldersForURL: nsURL 368 onlySubscribedFolders: subscribedOnly]; 369 370 return folders; 371} 372 373// 374// 375// 376- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode 377{ 378 NSMutableArray *folderPaths, *namespaces; 379 NSArray *folders, *mainFolders; 380 NSString *namespace; 381 382 BOOL subscribedOnly; 383 int count, max; 384 385 if (theListingMode == SOGoMailStandardListing) 386 subscribedOnly = [[[context activeUser] userDefaults] mailShowSubscribedFoldersOnly]; 387 else 388 { 389 subscribedOnly = NO; 390 DESTROY(subscribedFolders); 391 subscribedFolders = [[NSMutableDictionary alloc] init]; 392 folders = [[self imap4Connection] allFoldersForURL: [self imap4URL] 393 onlySubscribedFolders: YES]; 394 max = [folders count]; 395 for (count = 0; count < max; count++) 396 { 397 [subscribedFolders setObject: [NSNull null] 398 forKey: [folders objectAtIndex: count]]; 399 } 400 [[self imap4Connection] flushFolderHierarchyCache]; 401 } 402 403 mainFolders = [[NSArray arrayWithObjects: 404 [self inboxFolderNameInContext: context], 405 [self draftsFolderNameInContext: context], 406 [self sentFolderNameInContext: context], 407 [self trashFolderNameInContext: context], 408 [self junkFolderNameInContext: context], 409 nil] stringsWithFormat: @"/%@"]; 410 folders = [[self imap4Connection] allFoldersForURL: [self imap4URL] 411 onlySubscribedFolders: subscribedOnly]; 412 folderPaths = [folders mutableCopy]; 413 [folderPaths autorelease]; 414 415 [folderPaths removeObjectsInArray: mainFolders]; 416 namespaces = [NSMutableArray arrayWithCapacity: 10]; 417 [self _appendNamespaces: namespaces]; 418 max = [namespaces count]; 419 for (count = 0; count < max; count++) 420 { 421 namespace = [namespaces objectAtIndex: count]; 422 folders = [self _allFoldersFromNS: namespace 423 subscribedOnly: subscribedOnly]; 424 if ([folders count]) 425 { 426 [folderPaths removeObjectsInArray: folders]; 427 [folderPaths addObjectsFromArray: folders]; 428 429 // We make sure our "shared" / "public" namespace is always defined. Cyrus does NOT 430 // return them in LIST while Dovecot does. 431 namespace = [NSString stringWithFormat: @"/%@", namespace]; 432 [folderPaths removeObject: namespace]; 433 [folderPaths addObject: namespace]; 434 } 435 } 436 [folderPaths sortUsingSelector: @selector (localizedCaseInsensitiveCompare:)]; 437 [folderPaths replaceObjectsInRange: NSMakeRange (0, 0) 438 withObjectsFromArray: mainFolders]; 439 440 return folderPaths; 441} 442 443// 444// 445// 446- (NSString *) _folderType: (NSString *) folderName 447 flags: (NSMutableArray *) flags 448{ 449 static NSDictionary *metadata = nil; 450 NSString *folderType, *key, *rights; 451 SOGoUserDefaults *ud; 452 453 if (!metadata) 454 { 455 ud = [[context activeUser] userDefaults]; 456 metadata = [[[self imap4Connection] allFoldersMetadataForURL: [self imap4URL] 457 onlySubscribedFolders: [ud mailShowSubscribedFoldersOnly]] 458 objectForKey: @"list"]; 459 [metadata retain]; 460 } 461 462 key = [NSString stringWithFormat: @"/%@", folderName]; 463 [flags addObjectsFromArray: [metadata objectForKey: key]]; 464 465 // RFC6154 (https://tools.ietf.org/html/rfc6154) describes special uses for IMAP mailboxes. 466 // We do honor them, as long as your SOGo{Drafts,Trash,Sent,Junk}FolderName are properly configured 467 // See http://wiki.dovecot.org/MailboxSettings for a Dovecot example. 468 if ([folderName isEqualToString: inboxFolderName]) 469 folderType = @"inbox"; 470 else if ([flags containsObject: [self draftsFolderNameInContext: context]] || 471 [folderName isEqualToString: [self draftsFolderNameInContext: context]]) 472 folderType = @"draft"; 473 else if ([flags containsObject: [self sentFolderNameInContext: context]] || 474 [folderName isEqualToString: [self sentFolderNameInContext: context]]) 475 folderType = @"sent"; 476 else if ([flags containsObject: [self trashFolderNameInContext: context]] || 477 [folderName isEqualToString: [self trashFolderNameInContext: context]]) 478 folderType = @"trash"; 479 else if ([flags containsObject: [self junkFolderNameInContext: context]] || 480 [folderName isEqualToString: [self junkFolderNameInContext: context]]) 481 folderType = @"junk"; 482 else if ([folderName isEqualToString: otherUsersFolderName]) 483 folderType = @"otherUsers"; 484 else if ([folderName isEqualToString: sharedFoldersName]) 485 folderType = @"shared"; 486 else 487 { 488 folderType = @"folder"; 489 if (([sharedFoldersName length] && [folderName hasPrefix: sharedFoldersName]) || 490 ([otherUsersFolderName length] && [folderName hasPrefix: otherUsersFolderName])) 491 { 492 rights = [[self imap4Connection] myRightsForMailboxAtURL: [NSURL URLWithString: folderName]]; 493 if (![rights isKindOfClass: [NSException class]] && 494 ([rights rangeOfString: @"r"].location == NSNotFound)) 495 { 496 [flags addObjectUniquely: @"noselect"]; 497 if ([rights rangeOfString: @"i"].location != NSNotFound) 498 // No read but insert = dropbox 499 folderType = @"dropbox"; 500 } 501 } 502 } 503 504 return folderType; 505} 506 507// 508// 509// 510- (NSMutableDictionary *) _insertFolder: (NSString *) folderPath 511 foldersList: (NSMutableArray *) theFolders 512{ 513 NSArray *pathComponents; 514 NSMutableArray *folders, *flags; 515 NSMutableDictionary *currentFolder, *parentFolder, *folder; 516 NSString *currentFolderName, *currentPath, *sievePath, *fullName, *folderType; 517 SOGoUserManager *userManager; 518 519 BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder, isSubscribed; 520 int i, j, count; 521 522 parentFolder = nil; 523 parentIsOtherUsersFolder = NO; 524 pathComponents = [folderPath pathComponents]; 525 count = [pathComponents count]; 526 currentPath = @""; 527 528 // Make sure all ancestors exist. 529 // The variable folderPath is something like '/INBOX/Junk' so pathComponents becomes ('/', 'INBOX', 'Junk'). 530 // That's why we always ignore the first element 531 for (i = 1; i < count; i++) 532 { 533 last = ((count - i) == 1); 534 folder = nil; 535 if ([currentPath length]) 536 currentPath = [NSString stringWithFormat: @"%@/%@", currentPath, [pathComponents objectAtIndex: i]]; 537 else 538 currentPath = [pathComponents objectAtIndex: i]; 539 540 // Search for the current path in the children of the parent folder. 541 // For the first iteration, take the parent folder passed as argument. 542 if (parentFolder) 543 folders = [parentFolder objectForKey: @"children"]; 544 else 545 folders = theFolders; 546 547 for (j = 0; j < [folders count]; j++) 548 { 549 currentFolder = [folders objectAtIndex: j]; 550 if ([currentPath isEqualToString: [currentFolder objectForKey: @"path"]]) 551 { 552 folder = currentFolder; 553 // Make sure all branches are ready to receive children 554 if (!last && ![folder objectForKey: @"children"]) 555 [folder setObject: [NSMutableArray array] forKey: @"children"]; 556 557 break; 558 } 559 } 560 561 // Check if the current folder is the "Other users" folder (shared mailboxes) 562 currentFolderName = [[pathComponents objectAtIndex: i] stringByDecodingImap4FolderName]; 563 if (otherUsersFolderName 564 && [currentFolderName caseInsensitiveCompare: otherUsersFolderName] == NSOrderedSame) 565 isOtherUsersFolder = YES; 566 else 567 isOtherUsersFolder = NO; 568 569 if (folder == nil) 570 { 571 // Folder was not found; create it and add it to the folders list 572 if (parentIsOtherUsersFolder) 573 { 574 // Parent folder is the "Other users" folder; translate the user's mailbox name 575 // to the full name of the person 576 userManager = [SOGoUserManager sharedUserManager]; 577 if ((fullName = [userManager getCNForUID: currentFolderName]) && [fullName length]) 578 currentFolderName = fullName; 579 else if ((fullName = [userManager getEmailForUID: currentFolderName]) && [fullName length]) 580 currentFolderName = fullName; 581 } 582 else if (isOtherUsersFolder) 583 currentFolderName = [self labelForKey: @"OtherUsersFolderName"]; 584 else if (sharedFoldersName 585 && [currentFolderName caseInsensitiveCompare: sharedFoldersName] == NSOrderedSame) 586 currentFolderName = [self labelForKey: @"SharedFoldersName"]; 587 588 flags = [NSMutableArray array]; 589 590 if (last) 591 { 592 folderType = [self _folderType: currentPath 593 flags: flags]; 594 } 595 else 596 { 597 folderType = @"additional"; 598 [flags addObject: @"noselect"]; 599 } 600 601 if ([subscribedFolders objectForKey: folderPath]) 602 isSubscribed = YES; 603 else 604 isSubscribed = NO; 605 606 folder = [NSMutableDictionary dictionaryWithObjectsAndKeys: 607 currentPath, @"path", 608 folderType, @"type", 609 currentFolderName, @"name", 610 [NSMutableArray array], @"children", 611 flags, @"flags", 612 [NSNumber numberWithBool: isSubscribed], @"subscribed", 613 nil]; 614 615 if (sieveFolderUTF8Encoding) 616 sievePath = [currentPath stringByDecodingImap4FolderName]; 617 else 618 sievePath = currentPath; 619 [folder setObject: sievePath forKey: @"sievePath"]; 620 621 // Either add this new folder to its parent or the list of root folders 622 [folders addObject: folder]; 623 } 624 625 parentFolder = folder; 626 parentIsOtherUsersFolder = isOtherUsersFolder; 627 } 628 629 return parentFolder; 630} 631 632// 633// Return a tree representation of the mailboxes 634// 635- (NSArray *) allFoldersMetadata: (SOGoMailListingMode) theListingMode 636{ 637 NSString *currentFolder; 638 NSMutableArray *folders; 639 NSEnumerator *rawFolders; 640 NSAutoreleasePool *pool; 641 NSArray *allFolderPaths; 642 643 allFolderPaths = [self allFolderPaths: theListingMode]; 644 rawFolders = [allFolderPaths objectEnumerator]; 645 folders = [NSMutableArray array]; 646 647 while ((currentFolder = [rawFolders nextObject])) 648 { 649 // Using a local pool to avoid using too many file descriptors. This could 650 // happen with tons of mailboxes under "Other Users" as LDAP connections 651 // are never reused and "autoreleased" at the end. This loop would consume 652 // lots of LDAP connections during its execution. 653 pool = [[NSAutoreleasePool alloc] init]; 654 655 // Insert folder into folders tree 656 [self _insertFolder: currentFolder 657 foldersList: folders]; 658 659 [pool release]; 660 } 661 662 return folders; 663} 664 665- (NSDictionary *) _mailAccount 666{ 667 NSDictionary *mailAccount; 668 NSArray *accounts; 669 SOGoUser *user; 670 671 user = [SOGoUser userWithLogin: [self ownerInContext: nil]]; 672 accounts = [user mailAccounts]; 673 mailAccount = [accounts objectAtIndex: [nameInContainer intValue]]; 674 675 return mailAccount; 676} 677 678- (void) _appendDelegatorIdentities 679{ 680 NSArray *delegators; 681 SOGoUser *delegatorUser; 682 NSDictionary *delegatorAccount; 683 NSInteger count, max; 684 685 delegators = [[SOGoUser userWithLogin: owner] mailDelegators]; 686 max = [delegators count]; 687 for (count = 0; count < max; count++) 688 { 689 delegatorUser = [SOGoUser 690 userWithLogin: [delegators objectAtIndex: count]]; 691 if (delegatorUser) 692 { 693 delegatorAccount = [[delegatorUser mailAccounts] 694 objectAtIndex: 0]; 695 [identities addObjectsFromArray: 696 [delegatorAccount objectForKey: @"identities"]]; 697 } 698 } 699} 700 701- (NSArray *) identities 702{ 703 if (!identities) 704 { 705 identities = [[[self _mailAccount] objectForKey: @"identities"] 706 mutableCopy]; 707 if ([nameInContainer isEqualToString: @"0"]) 708 [self _appendDelegatorIdentities]; 709 } 710 711 return identities; 712} 713 714- (NSDictionary *) defaultIdentity 715{ 716 NSDictionary *defaultIdentity, *currentIdentity; 717 unsigned int count, max; 718 719 defaultIdentity = nil; 720 [self identities]; 721 722 max = [identities count]; 723 for (count = 0; count < max; count++) 724 { 725 currentIdentity = [identities objectAtIndex: count]; 726 if ([[currentIdentity objectForKey: @"isDefault"] boolValue]) 727 { 728 defaultIdentity = currentIdentity; 729 break; 730 } 731 } 732 733 return defaultIdentity; // can be nil 734} 735 736- (BOOL) forceDefaultIdentity 737{ 738 return [[[self _mailAccount] objectForKey: @"forceDefaultIdentity"] boolValue]; 739} 740 741- (NSDictionary *) identityForEmail: (NSString *) email 742{ 743 NSDictionary *identity, *currentIdentity; 744 NSString *currentEmail; 745 unsigned int count, max; 746 747 identity = nil; 748 [self identities]; 749 750 max = [identities count]; 751 for (count = 0; count < max; count++) 752 { 753 currentIdentity = [identities objectAtIndex: count]; 754 currentEmail = [currentIdentity objectForKey: @"email"]; 755 if ([currentEmail caseInsensitiveCompare: email] == NSOrderedSame) 756 { 757 identity = currentIdentity; 758 break; 759 } 760 } 761 762 return identity; // can be nil 763} 764 765- (NSString *) signature 766{ 767 NSDictionary *identity; 768 NSString *signature; 769 770 identity = [self defaultIdentity]; 771 772 if (identity) 773 signature = [identity objectForKey: @"signature"]; 774 else 775 signature = nil; 776 777 return signature; 778} 779 780- (NSString *) encryption 781{ 782 NSString *encryption; 783 784 encryption = [[self _mailAccount] objectForKey: @"encryption"]; 785 if (![encryption length]) 786 encryption = @"none"; 787 788 return encryption; 789} 790 791- (NSString *) tlsVerifyMode 792{ 793 NSString *verifyMode; 794 795 verifyMode = [[self _mailAccount] objectForKey: @"tlsVerifyMode"]; 796 if (!verifyMode || ![verifyMode length]) 797 verifyMode = @"default"; 798 799 return verifyMode; 800} 801 802- (NSMutableString *) imap4URLString 803{ 804 NSMutableString *imap4URLString; 805 NSDictionary *mailAccount; 806 NSString *encryption, *protocol, *username, *escUsername; 807 int defaultPort, port; 808 809 mailAccount = [self _mailAccount]; 810 encryption = [mailAccount objectForKey: @"encryption"]; 811 defaultPort = 143; 812 protocol = @"imap"; 813 814 if ([encryption isEqualToString: @"ssl"]) 815 { 816 protocol = @"imaps"; 817 defaultPort = 993; 818 } 819 else if ([encryption isEqualToString: @"tls"]) 820 { 821 protocol = @"imaps"; 822 } 823 824 username = [mailAccount objectForKey: @"userName"]; 825 escUsername 826 = [[username stringByEscapingURL] stringByReplacingString: @"@" 827 withString: @"%40"]; 828 imap4URLString = [NSMutableString stringWithFormat: @"%@://%@@%@", 829 protocol, escUsername, 830 [mailAccount objectForKey: @"serverName"]]; 831 port = [[mailAccount objectForKey: @"port"] intValue]; 832 if (port && port != defaultPort) 833 [imap4URLString appendFormat: @":%d", port]; 834 835 [imap4URLString appendString: @"/"]; 836 837 return imap4URLString; 838} 839 840- (NSMutableString *) traversalFromMailAccount 841{ 842 return [NSMutableString string]; 843} 844 845// 846// Extract password from basic authentication. 847// 848- (NSString *) imap4PasswordRenewed: (BOOL) renewed 849{ 850 NSString *password; 851 NSURL *imapURL; 852 853 // Default account - ie., the account that is provided with a default 854 // SOGo installation. User-added IMAP accounts will have name >= 1. 855 if ([nameInContainer isEqualToString: @"0"]) 856 { 857 imapURL = [self imap4URL]; 858 859 password = [[self authenticatorInContext: context] 860 imapPasswordInContext: context 861 forURL: imapURL 862 forceRenew: renewed]; 863 if (!password) 864 [self errorWithFormat: @"no IMAP4 password available"]; 865 } 866 else 867 { 868 password = [[self _mailAccount] objectForKey: @"password"]; 869 if (!password) 870 password = @""; 871 } 872 873 return password; 874} 875 876 877- (NSDictionary *) imapFolderGUIDs 878{ 879 NSDictionary *result, *nresult; 880 NSMutableDictionary *folders; 881 NGImap4Client *client; 882 SOGoUserDefaults *ud; 883 NSArray *folderList; 884 NSEnumerator *e; 885 NSString *guid; 886 id currentFolder; 887 888 BOOL hasAnnotatemore; 889 890 ud = [[context activeUser] userDefaults]; 891 892 // We skip the Junk folder here, as EAS doesn't know about this 893 if ([ud synchronizeOnlyDefaultMailFolders]) 894 folderList = [[NSArray arrayWithObjects: 895 [self inboxFolderNameInContext: context], 896 [self draftsFolderNameInContext: context], 897 [self sentFolderNameInContext: context], 898 [self trashFolderNameInContext: context], 899 nil] stringsWithFormat: @"/%@"]; 900 else 901 folderList = [self allFolderPaths: SOGoMailStandardListing]; 902 903 folders = [NSMutableDictionary dictionary]; 904 905 client = [[self imap4Connection] client]; 906 hasAnnotatemore = [self hasCapability: @"annotatemore"]; 907 908 if (hasAnnotatemore) 909 result = [client annotation: @"*" entryName: @"/comment" attributeName: @"value.priv"]; 910 else 911 result = [client lstatus: @"*" flags: [NSArray arrayWithObjects: @"x-guid", nil]]; 912 913 e = [folderList objectEnumerator]; 914 915 while ((currentFolder = [[e nextObject] substringFromIndex: 1])) 916 { 917 if (hasAnnotatemore) 918 guid = [[[[result objectForKey: @"FolderList"] objectForKey: currentFolder] objectForKey: @"/comment"] objectForKey: @"value.priv"]; 919 else 920 guid = [[[result objectForKey: @"status"] objectForKey: currentFolder] objectForKey: @"x-guid"]; 921 922 if (!guid || ![guid isNotNull]) 923 { 924 // Don't generate a GUID for "Other users" and "Shared" namespace folders - user foldername instead 925 if ((otherUsersFolderName && [currentFolder isEqualToString: otherUsersFolderName]) || 926 (sharedFoldersName && [currentFolder isEqualToString: sharedFoldersName])) 927 guid = [NSString stringWithFormat: @"%@", currentFolder]; 928 // If Dovecot is used with mail_shared_explicit_inbox = yes we have to generate a guid for "shared/user". 929 // * LIST (\NonExistent \HasChildren) "/" shared 930 // * LIST (\NonExistent \HasChildren) "/" shared/jdoe@example.com 931 // * LIST (\HasNoChildren) "/" shared/jdoe@example.com/INBOX 932 else if (!hasAnnotatemore && 933 (([[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"nonexistent"] != NSNotFound || 934 [[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"noselect"] != NSNotFound)&& 935 [[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"haschildren"] != NSNotFound)) 936 guid = [NSString stringWithFormat: @"%@", currentFolder]; 937 else 938 { 939 // If folder doesn't exists - ignore it. 940 nresult = [client status: currentFolder 941 flags: [NSArray arrayWithObject: @"UIDVALIDITY"]]; 942 if (![[nresult valueForKey: @"result"] boolValue]) 943 continue; 944 else if (hasAnnotatemore) 945 { 946 guid = [[NSProcessInfo processInfo] globallyUniqueString]; 947 nresult = [client annotation: currentFolder entryName: @"/comment" attributeName: @"value.priv" attributeValue: guid]; 948 } 949 950 // setannotation failed or annotatemore is not available 951 if ((hasAnnotatemore && ![[nresult objectForKey: @"result"] boolValue]) || !hasAnnotatemore) 952 guid = [NSString stringWithFormat: @"%@", currentFolder]; 953 } 954 } 955 956 [folders setObject: [NSString stringWithFormat: @"folder%@", guid] forKey: [NSString stringWithFormat: @"folder%@", currentFolder]]; 957 } 958 959 return folders; 960} 961 962 963/* name lookup */ 964 965- (id) lookupNameByPaths: (NSArray *) _paths 966 inContext: (id)_ctx 967 acquire: (BOOL) _flag 968{ 969 NSString *folderName; 970 NSUInteger count, max; 971 SOGoMailBaseObject *folder; 972 973 max = [_paths count]; 974 folder = self; 975 for (count = 0; folder && count < max; count++) 976 { 977 folderName = [_paths objectAtIndex: count]; 978 folder = [folder lookupName: folderName inContext: _ctx acquire: _flag]; 979 } 980 981 return folder; 982} 983 984- (id) lookupName: (NSString *) _key 985 inContext: (id)_ctx 986 acquire: (BOOL) _flag 987{ 988 NSString *folderName; 989 NSMutableArray *namespaces; 990 Class klazz; 991 id obj; 992 993 [[[self imap4Connection] client] namespace]; 994 995 if ([_key hasPrefix: @"folder"]) 996 { 997 folderName = [[_key substringFromIndex: 6] fromCSSIdentifier]; 998 999 namespaces = [NSMutableArray array]; 1000 [self _appendNamespaces: namespaces]; 1001 if ([namespaces containsObject: folderName]) 1002 klazz = [SOGoMailNamespace class]; 1003 else if ([folderName 1004 isEqualToString: [self draftsFolderNameInContext: _ctx]]) 1005 klazz = [SOGoDraftsFolder class]; 1006 else if ([folderName 1007 isEqualToString: [self sentFolderNameInContext: _ctx]]) 1008 klazz = [SOGoSentFolder class]; 1009 else if ([folderName 1010 isEqualToString: [self trashFolderNameInContext: _ctx]]) 1011 klazz = [SOGoTrashFolder class]; 1012 else if ([folderName 1013 isEqualToString: [self junkFolderNameInContext: _ctx]]) 1014 klazz = [SOGoJunkFolder class]; 1015 else 1016 klazz = [SOGoMailFolder class]; 1017 1018 obj = [klazz objectWithName: _key inContainer: self]; 1019 } 1020 else 1021 obj = [super lookupName: _key inContext: _ctx acquire: NO]; 1022 1023 /* return 404 to stop acquisition */ 1024 if (!obj) 1025 obj = [NSException exceptionWithHTTPStatus: 404 /* Not Found */]; 1026 1027 return obj; 1028} 1029 1030/* special folders */ 1031 1032- (NSString *) inboxFolderNameInContext: (id)_ctx 1033{ 1034 /* cannot be changed in Cyrus ? */ 1035 return inboxFolderName; 1036} 1037 1038- (NSString *) _userFolderNameWithPurpose: (NSString *) purpose 1039{ 1040 SOGoUser *user; 1041 NSArray *accounts; 1042 int accountIdx; 1043 NSDictionary *account; 1044 NSString *folderName; 1045 1046 folderName = nil; 1047 1048 user = [SOGoUser userWithLogin: [self ownerInContext: nil]]; 1049 accounts = [user mailAccounts]; 1050 accountIdx = [nameInContainer intValue]; 1051 account = [accounts objectAtIndex: accountIdx]; 1052 folderName = [[account objectForKey: @"specialMailboxes"] 1053 objectForKey: purpose]; 1054 if (!folderName && accountIdx > 0) 1055 { 1056 account = [accounts objectAtIndex: 0]; 1057 folderName = [[account objectForKey: @"specialMailboxes"] 1058 objectForKey: purpose]; 1059 } 1060 1061 return folderName; 1062} 1063 1064- (NSString *) draftsFolderNameInContext: (id) _ctx 1065{ 1066 return [self _userFolderNameWithPurpose: @"Drafts"]; 1067} 1068 1069- (NSString *) sentFolderNameInContext: (id)_ctx 1070{ 1071 return [self _userFolderNameWithPurpose: @"Sent"]; 1072} 1073 1074- (NSString *) trashFolderNameInContext: (id)_ctx 1075{ 1076 return [self _userFolderNameWithPurpose: @"Trash"]; 1077} 1078 1079- (NSString *) junkFolderNameInContext: (id)_ctx 1080{ 1081 return [self _userFolderNameWithPurpose: @"Junk"]; 1082} 1083 1084- (NSString *) otherUsersFolderNameInContext: (id)_ctx 1085{ 1086 return otherUsersFolderName; 1087} 1088 1089- (NSString *) sharedFoldersNameInContext: (id)_ctx 1090{ 1091 return sharedFoldersName; 1092} 1093 1094- (id) folderWithTraversal: (NSString *) traversal 1095 andClassName: (NSString *) className 1096{ 1097 NSArray *paths; 1098 NSString *currentName; 1099 id currentContainer; 1100 unsigned int count, max; 1101 Class clazz; 1102 1103 currentContainer = self; 1104 paths = [traversal componentsSeparatedByString: @"/"]; 1105 1106 if (!className) 1107 clazz = [SOGoMailFolder class]; 1108 else 1109 clazz = NSClassFromString (className); 1110 1111 max = [paths count]; 1112 for (count = 0; count < max - 1; count++) 1113 { 1114 currentName = [NSString stringWithFormat: @"folder%@", 1115 [paths objectAtIndex: count]]; 1116 currentContainer = [SOGoMailFolder objectWithName: currentName 1117 inContainer: currentContainer]; 1118 } 1119 currentName = [NSString stringWithFormat: @"folder%@", 1120 [paths objectAtIndex: max - 1]]; 1121 1122 return [clazz objectWithName: currentName inContainer: currentContainer]; 1123} 1124 1125- (SOGoMailFolder *) inboxFolderInContext: (id) _ctx 1126{ 1127 // TODO: use some profile to determine real location, use a -traverse lookup 1128 if (!inboxFolder) 1129 { 1130 inboxFolder 1131 = [self folderWithTraversal: [self inboxFolderNameInContext: _ctx] 1132 andClassName: nil]; 1133 [inboxFolder retain]; 1134 } 1135 1136 return inboxFolder; 1137} 1138 1139- (SOGoDraftsFolder *) draftsFolderInContext: (id) _ctx 1140{ 1141 // TODO: use some profile to determine real location, use a -traverse lookup 1142 1143 if (!draftsFolder) 1144 { 1145 draftsFolder 1146 = [self folderWithTraversal: [self draftsFolderNameInContext: _ctx] 1147 andClassName: @"SOGoDraftsFolder"]; 1148 [draftsFolder retain]; 1149 } 1150 1151 return draftsFolder; 1152} 1153 1154- (SOGoSentFolder *) sentFolderInContext: (id) _ctx 1155{ 1156 // TODO: use some profile to determine real location, use a -traverse lookup 1157 1158 if (!sentFolder) 1159 { 1160 sentFolder 1161 = [self folderWithTraversal: [self sentFolderNameInContext: _ctx] 1162 andClassName: @"SOGoSentFolder"]; 1163 [sentFolder retain]; 1164 } 1165 1166 return sentFolder; 1167} 1168 1169- (SOGoTrashFolder *) trashFolderInContext: (id) _ctx 1170{ 1171 if (!trashFolder) 1172 { 1173 trashFolder 1174 = [self folderWithTraversal: [self trashFolderNameInContext: _ctx] 1175 andClassName: @"SOGoTrashFolder"]; 1176 [trashFolder retain]; 1177 } 1178 1179 return trashFolder; 1180} 1181 1182- (SOGoJunkFolder *) junkFolderInContext: (id) _ctx 1183{ 1184 if (!junkFolder) 1185 { 1186 junkFolder 1187 = [self folderWithTraversal: [self junkFolderNameInContext: _ctx] 1188 andClassName: @"SOGoJunkFolder"]; 1189 [trashFolder retain]; 1190 } 1191 1192 return junkFolder; 1193} 1194 1195/* account delegation */ 1196- (NSArray *) delegates 1197{ 1198 NSDictionary *mailSettings; 1199 SOGoUser *ownerUser; 1200 NSArray *delegates; 1201 1202 if ([nameInContainer isEqualToString: @"0"]) 1203 { 1204 ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]]; 1205 mailSettings = [[ownerUser userSettings] objectForKey: @"Mail"]; 1206 delegates = [mailSettings objectForKey: @"DelegateTo"]; 1207 if (!delegates) 1208 delegates = [NSArray array]; 1209 } 1210 else 1211 delegates = nil; 1212 1213 return delegates; 1214} 1215 1216- (void) _setDelegates: (NSArray *) newDelegates 1217{ 1218 NSMutableDictionary *mailSettings; 1219 SOGoUser *ownerUser; 1220 SOGoUserSettings *settings; 1221 1222 ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]]; 1223 settings = [ownerUser userSettings]; 1224 mailSettings = [settings objectForKey: @"Mail"]; 1225 if (!mailSettings) 1226 { 1227 mailSettings = [NSMutableDictionary dictionaryWithCapacity: 1]; 1228 [settings setObject: mailSettings forKey: @"Mail"]; 1229 } 1230 [mailSettings setObject: newDelegates 1231 forKey: @"DelegateTo"]; 1232 [settings synchronize]; 1233} 1234 1235- (void) addDelegates: (NSArray *) newDelegates 1236{ 1237 NSMutableArray *delegates; 1238 NSInteger count, max; 1239 NSString *currentDelegate; 1240 SOGoUser *delegateUser; 1241 1242 if ([nameInContainer isEqualToString: @"0"]) 1243 { 1244 delegates = [[self delegates] mutableCopy]; 1245 [delegates autorelease]; 1246 max = [newDelegates count]; 1247 for (count = 0; count < max; count++) 1248 { 1249 currentDelegate = [newDelegates objectAtIndex: count]; 1250 delegateUser = [SOGoUser userWithLogin: currentDelegate]; 1251 if (delegateUser) 1252 { 1253 [delegates addObjectUniquely: currentDelegate]; 1254 [delegateUser addMailDelegator: owner]; 1255 } 1256 } 1257 1258 [self _setDelegates: delegates]; 1259 } 1260} 1261 1262- (void) removeDelegates: (NSArray *) oldDelegates 1263{ 1264 NSMutableArray *delegates; 1265 NSInteger count, max; 1266 NSString *currentDelegate; 1267 SOGoUser *delegateUser; 1268 1269 if ([nameInContainer isEqualToString: @"0"]) 1270 { 1271 delegates = [[self delegates] mutableCopy]; 1272 [delegates autorelease]; 1273 max = [oldDelegates count]; 1274 for (count = 0; count < max; count++) 1275 { 1276 currentDelegate = [oldDelegates objectAtIndex: count]; 1277 delegateUser = [SOGoUser userWithLogin: currentDelegate]; 1278 [delegates removeObject: currentDelegate]; 1279 if (delegateUser) 1280 [delegateUser removeMailDelegator: owner]; 1281 } 1282 1283 [self _setDelegates: delegates]; 1284 } 1285} 1286 1287/* WebDAV */ 1288 1289- (NSString *) davContentType 1290{ 1291 return @"httpd/unix-directory"; 1292} 1293 1294- (BOOL) davIsCollection 1295{ 1296 return YES; 1297} 1298 1299- (NSException *) davCreateCollection: (NSString *) _name 1300 inContext: (id) _ctx 1301{ 1302 return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]]; 1303} 1304 1305- (NSString *) davDisplayName 1306{ 1307 return [[self _mailAccount] objectForKey: @"name"]; 1308} 1309 1310- (NSData *) certificate 1311{ 1312 NSData *mailCertificate; 1313 SOGoUserDefaults *ud; 1314 1315 mailCertificate = nil; 1316 ud = [[context activeUser] userDefaults]; 1317 1318 if ([nameInContainer isEqualToString: @"0"]) 1319 { 1320 mailCertificate = [[ud stringForKey: @"SOGoMailCertificate"] dataByDecodingBase64]; 1321 } 1322 else 1323 { 1324 int accountIdx; 1325 NSArray* accounts; 1326 NSDictionary *account, *security; 1327 1328 accountIdx = [nameInContainer intValue] - 1; 1329 accounts = [ud auxiliaryMailAccounts]; 1330 if ([accounts count] > accountIdx) 1331 { 1332 account = [accounts objectAtIndex: accountIdx]; 1333 security = [account objectForKey: @"security"]; 1334 if (security && [[security objectForKey: @"certificate"] length]) 1335 { 1336 mailCertificate = [[security objectForKey: @"certificate"] dataByDecodingBase64]; 1337 } 1338 } 1339 } 1340 1341 return mailCertificate; 1342} 1343 1344- (void) setCertificate: (NSData *) theData 1345{ 1346 SOGoUserDefaults *ud; 1347 1348 ud = [[context activeUser] userDefaults]; 1349 1350 if ([nameInContainer isEqualToString: @"0"]) 1351 { 1352 if ([theData length]) 1353 [ud setObject: [theData stringByEncodingBase64] forKey: @"SOGoMailCertificate"]; 1354 else 1355 [ud removeObjectForKey: @"SOGoMailCertificate"]; 1356 } 1357 else 1358 { 1359 int accountIdx; 1360 NSArray* accounts; 1361 NSMutableDictionary *account, *security; 1362 1363 accountIdx = [nameInContainer intValue] - 1; 1364 accounts = [ud auxiliaryMailAccounts]; 1365 if ([accounts count] > accountIdx) 1366 { 1367 account = [accounts objectAtIndex: accountIdx]; 1368 security = [account objectForKey: @"security"]; 1369 if (!security) 1370 { 1371 security = [NSMutableDictionary dictionary]; 1372 [account setObject: security forKey: @"security"]; 1373 } 1374 if ([theData length]) 1375 [security setObject: [theData stringByEncodingBase64] forKey: @"certificate"]; 1376 else 1377 [security removeObjectForKey: @"certificate"]; 1378 [ud setAuxiliaryMailAccounts: accounts]; 1379 } 1380 } 1381 1382 [ud synchronize]; 1383} 1384 1385 1386@end /* SOGoMailAccount */ 1387