* Adium is the legal property of its developers, whose names are listed in the copyright file included * with this source distribution. * This program is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * You should have received a copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #import "ESSecureMessagingPlugin.h" #import "AdiumOTREncryption.h" #import <Adium/AIChatControllerProtocol.h> #import <Adium/AIContentControllerProtocol.h> #import <Adium/AIInterfaceControllerProtocol.h> #import <Adium/AIMenuControllerProtocol.h> #import <Adium/AIToolbarControllerProtocol.h> #import <AIUtilities/AIMenuAdditions.h> #import <AIUtilities/AIToolbarUtilities.h> #import <AIUtilities/AIImageAdditions.h> #import <AIUtilities/AIImageAdditions.h> #import <AIUtilities/MVMenuButton.h> #import <AIUtilities/AIStringAdditions.h> #import <Adium/AIAccount.h> #import <Adium/AIListContact.h> #define TITLE_MAKE_SECURE AILocalizedString(@"Initiate Encrypted OTR Chat",nil) #define TITLE_MAKE_INSECURE AILocalizedString(@"Cancel Encrypted Chat",nil) #define TITLE_SHOW_DETAILS [AILocalizedString(@"Show Details",nil) stringByAppendingEllipsis] #define TITLE_VERIFY [AILocalizedString(@"Verify",nil) stringByAppendingEllipsis] #define TITLE_ENCRYPTION_OPTIONS AILocalizedString(@"Encryption Settings",nil) #define TITLE_ABOUT_ENCRYPTION [AILocalizedString(@"About Encryption",nil) stringByAppendingEllipsis] #define TITLE_ENCRYPTION AILocalizedString(@"Encryption",nil) #define CHAT_NOW_SECURE AILocalizedString(@"Encrypted OTR chat initiated.", nil) #define CHAT_NOW_SECURE_UNVERIFIED AILocalizedString(@"Encrypted OTR chat initiated. %@'s identity not verified.", nil) #define CHAT_NO_LONGER_SECURE AILocalizedString(@"Ended encrypted OTR chat.", nil) @interface ESSecureMessagingPlugin () - (void)configureMenuItems; - (void)registerToolbarItem; - (NSMenu *)_secureMessagingMenu; - (void)_updateToolbarIconOfChat:(AIChat *)inChat inWindow:(NSWindow *)window; - (void)_updateToolbarItem:(NSToolbarItem *)item forChat:(AIChat *)chat; - (void) toolbarDidAddItem:(NSToolbarItem *)item; - (IBAction)toggleSecureMessaging:(id)sender; - (void)chatDidBecomeVisible:(NSNotification *)notification; - (void)dummyAction:(id)sender; @implementation ESSecureMessagingPlugin //Muy imporatante: Set OTR as our encryption method [adium.contentController setEncryptor:[[[AdiumOTREncryption alloc] init] autorelease]]; _secureMessagingMenu = nil; lockImage_Locked = [[NSImage imageNamed:@"lock-locked" forClass:[self class]] retain]; lockImage_Unlocked = [[NSImage imageNamed:@"lock-unlocked" forClass:[self class]] retain]; [self registerToolbarItem]; [self configureMenuItems]; [adium.chatController registerChatObserver:self]; [adium.chatController unregisterChatObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self]; - (void)configureMenuItems NSMenu *menu = [self _secureMessagingMenu]; //Add menu to toolbar item (for text mode) menuItem_encryption = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:AILocalizedString(@"Encryption", nil) action:@selector(dummyAction:) [menuItem_encryption setSubmenu:menu]; [menuItem_encryption setTag:AISecureMessagingMenu_Root]; [adium.menuController addMenuItem:menuItem_encryption toLocation:LOC_Contact_Additions]; menuItem_encryptionContext = [menuItem_encryption copy]; [adium.menuController addContextualMenuItem:menuItem_encryptionContext toLocation:Context_Contact_ChatAction]; - (void)registerToolbarItem toolbarItems = [[NSMutableSet alloc] init]; //Toolbar item registration [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(toolbarWillAddItem:) name:NSToolbarWillAddItemNotification [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(toolbarDidRemoveItem:) name:NSToolbarDidRemoveItemNotification //Register our toolbar item NSToolbarItem *toolbarItem; button = [[[MVMenuButton alloc] initWithFrame:NSMakeRect(0,0,32,32)] autorelease]; [button setImage:lockImage_Locked]; toolbarItem = [AIToolbarUtilities toolbarItemWithIdentifier:@"Encryption" paletteLabel:AILocalizedString(@"Encrypted Messaging",nil) toolTip:AILocalizedString(@"Toggle encrypted messaging. Shows a closed lock when secure and an open lock when insecure.",nil) settingSelector:@selector(setView:) action:@selector(toggleSecureMessaging:) [toolbarItem setMinSize:NSMakeSize(32,32)]; [toolbarItem setMaxSize:NSMakeSize(32,32)]; [button setToolbarItem:toolbarItem]; //Register our toolbar item [adium.toolbarController registerToolbarItem:toolbarItem forToolbarType:@"MessageWindow"]; //After the toolbar has added the item we can set up the submenus - (void)toolbarWillAddItem:(NSNotification *)notification NSToolbarItem *item = [[notification userInfo] objectForKey:@"item"]; if ([[item itemIdentifier] isEqualToString:@"Encryption"]) { //If this is the first item added, start observing for chats becoming visible so we can update the icon if ([toolbarItems count] == 0) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(chatDidBecomeVisible:) name:@"AIChatDidBecomeVisible" NSMenu *menu = [self _secureMessagingMenu]; [[item view] setMenu:menu]; //Add menu to toolbar item (for text mode) NSMenuItem *mItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] init] autorelease]; [mItem setTitle:[menu title]]; [item setMenuFormRepresentation:mItem]; [toolbarItems addObject:item]; [self performSelector:@selector(toolbarDidAddItem:) - (void)toolbarDidAddItem:(NSToolbarItem *)item /* Only need to take action if we haven't already validated the initial state of this item. * This will only be true when the toolbar is revealed for the first time having been hidden when window opened. if (![validatedItems containsObject:item]) { NSEnumerator *enumerator = [[NSApp windows] objectEnumerator]; NSToolbar *thisItemsToolbar = [item toolbar]; //Look at each window to find the toolbar we are in while ((window = [enumerator nextObject])) { if ([window toolbar] == thisItemsToolbar) break; [self _updateToolbarItem:item forChat:[adium.interfaceController activeChatInWindow:window]]; - (void)toolbarDidRemoveItem: (NSNotification *)notification NSToolbarItem *item = [[notification userInfo] objectForKey:@"item"]; if ([toolbarItems containsObject:item]) { [toolbarItems removeObject:item]; [validatedItems removeObject:item]; if ([toolbarItems count] == 0) { [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AIChatDidBecomeVisible" //A chat became visible in a window. Update the item with the @"Encryption" identifier to show the IsSecure state for this chat - (void)chatDidBecomeVisible:(NSNotification *)notification [self _updateToolbarIconOfChat:[notification object] inWindow:[[notification userInfo] objectForKey:@"NSWindow"]]; //When the IsSecure key of a chat changes, update the @"Encryption" item immediately - (NSSet *)updateChat:(AIChat *)inChat keys:(NSSet *)inModifiedKeys silent:(BOOL)silent if ([inModifiedKeys containsObject:@"securityDetails"]) { [self _updateToolbarIconOfChat:inChat inWindow:[adium.interfaceController windowForChat:inChat]]; /* Add a status message to the chat */ BOOL chatIsSecure = [inChat isSecure]; if (chatIsSecure != [inChat boolValueForProperty:@"secureMessagingLastEncryptedState"]) { [inChat setValue:[NSNumber numberWithBool:chatIsSecure] forProperty:@"secureMessagingLastEncryptedState" if ([inChat encryptionStatus] == EncryptionStatus_Unverified) { AIListObject *listObject = [inChat listObject]; NSString *displayName = (listObject ? listObject.formattedUID : message = [NSString stringWithFormat:CHAT_NOW_SECURE_UNVERIFIED, displayName]; type = @"encryptionStartedUnverified"; message = CHAT_NOW_SECURE; type = @"encryptionStarted"; message = CHAT_NO_LONGER_SECURE; type = @"encryptionEnded"; [adium.contentController displayEvent:message - (void)_updateToolbarItem:(NSToolbarItem *)item forChat:(AIChat *)chat image = lockImage_Locked; image = lockImage_Unlocked; [item setEnabled:[chat supportsSecureMessagingToggling]]; [(MVMenuButton *)[item view] setImage:image]; [validatedItems addObject:item]; - (void)_updateToolbarIconOfChat:(AIChat *)chat inWindow:(NSWindow *)window NSToolbar *toolbar = [window toolbar]; NSEnumerator *enumerator = [[toolbar items] objectEnumerator]; while ((item = [enumerator nextObject])) { if ([[item itemIdentifier] isEqualToString:@"Encryption"]) { [self _updateToolbarItem:item forChat:chat]; - (IBAction)toggleSecureMessaging:(id)sender AIChat *chat = adium.interfaceController.activeChat; [chat.account requestSecureMessaging:!chat.isSecure - (IBAction)showDetails:(id)sender NSRunInformationalAlertPanel(AILocalizedString(@"Details",nil), [[adium.interfaceController.activeChat securityDetails] objectForKey:@"Description"], AILocalizedString(@"OK",nil), - (IBAction)verify:(id)sender AIChat *chat = adium.interfaceController.activeChat; [chat.account promptToVerifyEncryptionIdentityInChat:chat]; - (IBAction)showAbout:(id)sender NSString *aboutEncryption; aboutEncryption = adium.interfaceController.activeChat.account.aboutEncryption; NSRunInformationalAlertPanel(AILocalizedString(@"About Encryption",nil), AILocalizedString(@"OK",nil), - (IBAction)selectedEncryptionPreference:(id)sender AIListContact *listContact = adium.interfaceController.activeChat.listObject.parentContact; [listContact setPreference:[NSNumber numberWithInteger:[sender tag]] forKey:KEY_ENCRYPTED_CHAT_PREFERENCE //Disable the insertion if a text field is not active - (BOOL)validateMenuItem:(NSMenuItem *)menuItem if (menuItem == menuItem_encryptionContext) { chat = adium.menuController.currentContextMenuChat; chat = adium.interfaceController.activeChat; if ([[[menuItem menu] title] isEqualToString:ENCRYPTION_MENU_TITLE]) { AIEncryptedChatPreference tag = (AIEncryptedChatPreference)[menuItem tag]; AIListContact *listContact = chat.listObject.parentContact; AIEncryptedChatPreference userPreference = [[listContact preferenceForKey:KEY_ENCRYPTED_CHAT_PREFERENCE group:GROUP_ENCRYPTION] intValue]; case EncryptedChat_Default: //Set the state (checked or unchecked) as appropriate. Default = no pref or the actual 'default' value. [menuItem setState:(tag == userPreference || ![listContact preferenceForKey:KEY_ENCRYPTED_CHAT_PREFERENCE group:GROUP_ENCRYPTION])]; case EncryptedChat_Never: case EncryptedChat_Manually: case EncryptedChat_Automatically: case EncryptedChat_RejectUnencryptedMessages: //Set the state (checked or unchecked) as appropriate [menuItem setState:(tag == userPreference)]; /* Items on the main menu */ AISecureMessagingMenuTag tag = (AISecureMessagingMenuTag)[menuItem tag]; case AISecureMessagingMenu_Root: return [chat supportsSecureMessagingToggling]; case AISecureMessagingMenu_Toggle: // The menu item should indicate what will happen if it is selected.. the opposite of our secure state [menuItem setTitle:TITLE_MAKE_INSECURE]; [menuItem setTitle:TITLE_MAKE_SECURE]; AIListContact *listContact = chat.listObject.parentContact; AIEncryptedChatPreference userPreference = [[listContact preferenceForKey:KEY_ENCRYPTED_CHAT_PREFERENCE group:GROUP_ENCRYPTION] intValue]; // Disable 'Initiate Encrypted OTR Chat' menu item if chat encryption is disabled if (userPreference == EncryptedChat_Never) { case AISecureMessagingMenu_ShowDetails: case AISecureMessagingMenu_Verify: //Only enable show details if the chat is secure case AISecureMessagingMenu_Options: //Only enable options if the chat is with a single person return ([chat supportsSecureMessagingToggling] && chat.listObject && !chat.isGroupChat); case AISecureMessagingMenu_ShowAbout: return [chat supportsSecureMessagingToggling]; - (NSMenu *)_secureMessagingMenu if (!_secureMessagingMenu) { _secureMessagingMenu = [[NSMenu allocWithZone:[NSMenu menuZone]] init]; [_secureMessagingMenu setTitle:TITLE_ENCRYPTION]; item = [[[NSMenuItem alloc] initWithTitle:TITLE_MAKE_SECURE action:@selector(toggleSecureMessaging:) keyEquivalent:@""] autorelease]; [item setTag:AISecureMessagingMenu_Toggle]; [_secureMessagingMenu addItem:item]; item = [[[NSMenuItem alloc] initWithTitle:TITLE_SHOW_DETAILS action:@selector(showDetails:) keyEquivalent:@""] autorelease]; [item setTag:AISecureMessagingMenu_ShowDetails]; [_secureMessagingMenu addItem:item]; item = [[[NSMenuItem alloc] initWithTitle:TITLE_VERIFY action:@selector(verify:) keyEquivalent:@""] autorelease]; [item setTag:AISecureMessagingMenu_Verify]; [_secureMessagingMenu addItem:item]; item = [[[NSMenuItem alloc] initWithTitle:TITLE_ENCRYPTION_OPTIONS keyEquivalent:@""] autorelease]; [item setTag:AISecureMessagingMenu_Options]; [item setSubmenu:[adium.contentController encryptionMenuNotifyingTarget:self [_secureMessagingMenu addItem:item]; [_secureMessagingMenu addItem:[NSMenuItem separatorItem]]; item = [[[NSMenuItem alloc] initWithTitle:TITLE_ABOUT_ENCRYPTION action:@selector(showAbout:) keyEquivalent:@""] autorelease]; [item setTag:AISecureMessagingMenu_ShowAbout]; [_secureMessagingMenu addItem:item]; return [[_secureMessagingMenu copy] autorelease]; - (void)dummyAction:(id)sender {};