* 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 "AdiumOTREncryption.h" #import <Adium/AIContentMessage.h> #import <Adium/AIAccountControllerProtocol.h> #import <Adium/AIChatControllerProtocol.h> #import <Adium/AIContactControllerProtocol.h> #import <Adium/AIContentControllerProtocol.h> #import <Adium/AIInterfaceControllerProtocol.h> #import <Adium/AILoginControllerProtocol.h> #import <Adium/AIAccount.h> #import <Adium/AIService.h> #import <Adium/AIContentMessage.h> #import <Adium/AIListObject.h> #import <Adium/AIListContact.h> #import "AIHTMLDecoder.h" #import <AIUtilities/AIStringAdditions.h> #import "ESOTRPreferences.h" #import "ESOTRUnknownFingerprintController.h" #import "AIOTRSMPSecretAnswerWindowController.h" #import "AIOTRSMPSharedSecretWindowController.h" #import "AIOTRTopBarUnverifiedContactController.h" #import "AIMessageViewController.h" #define PRIVKEY_PATH [[[adium.loginController userDirectory] stringByAppendingPathComponent:@"otr.private_key"] UTF8String] #define STORE_PATH [[[adium.loginController userDirectory] stringByAppendingPathComponent:@"otr.fingerprints"] UTF8String] #define INSTAG_PATH [[[adium.loginController userDirectory] stringByAppendingPathComponent:@"otr.instag"] UTF8String] #define CLOSED_CONNECTION_MESSAGE "has closed his private connection to you" /* OTRL_POLICY_MANUAL doesn't let us respond to other users' automatic attempts at encryption. * If either user has OTR set to Automatic, an OTR session should be begun; without this modified * mask, both users would have to be on automatic for OTR to begin automatically, even though one user * _manually_ attempting OTR will _automatically_ bring the other into OTR even if the setting is Manual. #define OTRL_POLICY_MANUAL_AND_RESPOND_TO_WHITESPACE ( OTRL_POLICY_MANUAL | \ OTRL_POLICY_WHITESPACE_START_AKE | \ OTRL_POLICY_ERROR_START_AKE ) @interface AdiumOTREncryption () - ( void ) prepareEncryption ; - ( void ) setSecurityDetails: ( NSDictionary * ) securityDetailsDict forChat: ( AIChat * ) inChat ; - ( void ) upgradeOTRIfNeeded ; - ( void ) adiumFinishedLaunching: ( NSNotification * ) inNotification ; - ( void ) adiumWillTerminate: ( NSNotification * ) inNotification ; - ( void ) updateSecurityDetails: ( NSNotification * ) inNotification ; @implementation AdiumOTREncryption /* We'll only use the one OtrlUserState. */ static OtrlUserState otrg_plugin_userstate = NULL ; static AdiumOTREncryption * adiumOTREncryption = nil ; static OtrlMessageAppOps ui_ops ; void send_default_query_to_chat ( AIChat * inChat ); void disconnect_from_chat ( AIChat * inChat ); void disconnect_from_context ( ConnContext * context ); static OtrlMessageAppOps ui_ops ; TrustLevel otrg_plugin_context_to_trust ( ConnContext * context ); #pragma mark Singleton management if ( adiumOTREncryption ) { return [ adiumOTREncryption retain ]; if (( self = [ super init ])) { adiumOTREncryption = self ; //Wait for Adium to finish launching to prepare encryption so that accounts will be loaded [[ NSNotificationCenter defaultCenter ] addObserver : self selector : @selector ( adiumFinishedLaunching : ) name : AIApplicationDidFinishLoadingNotification - ( void ) adiumFinishedLaunching: ( NSNotification * ) inNotification [ self prepareEncryption ]; - ( void ) prepareEncryption /* Initialize the OTR library */ /* Make our OtrlUserState; we'll only use the one. */ otrg_plugin_userstate = otrl_userstate_create (); err = otrl_privkey_read ( otrg_plugin_userstate , PRIVKEY_PATH ); const char * errMsg = gpg_strerror ( err ); if ( errMsg && strcmp ( errMsg , "No such file or directory" )) { NSLog ( @"Error reading %s: %s" , PRIVKEY_PATH , errMsg ); otrg_ui_update_keylist (); err = otrl_privkey_read_fingerprints ( otrg_plugin_userstate , STORE_PATH , const char * errMsg = gpg_strerror ( err ); if ( errMsg && strcmp ( errMsg , "No such file or directory" )) { NSLog ( @"Error reading %s: %s" , STORE_PATH , errMsg ); otrg_ui_update_fingerprint (); [[ NSNotificationCenter defaultCenter ] addObserver : self selector : @selector ( adiumWillTerminate : ) name : AIAppWillTerminateNotification [[ NSNotificationCenter defaultCenter ] addObserver : self selector : @selector ( updateSecurityDetails : ) [[ NSNotificationCenter defaultCenter ] addObserver : self selector : @selector ( updateSecurityDetails : ) name : Chat_DestinationChanged [[ NSNotificationCenter defaultCenter ] addObserver : self selector : @selector ( updateSecurityDetails : ) //Add the Encryption preferences OTRPrefs = [( ESOTRPreferences * )[ ESOTRPreferences preferencePane ] retain ]; [[ NSNotificationCenter defaultCenter ] removeObserver : self ]; #pragma mark Lookup functions between OTR contexts and accounts/chats * @brief Return an NSDictionary* describing a ConnContext. * @"Their Fingerprint" : NSString of the contact's fingerprint's human-readable hash * @"Our Fingerprint" : NSString of our fingerprint's human-readable hash * @"Incoming SessionID" : NSString of the incoming sessionID * @"Outgoing SessionID" : NSString of the outgoing sessionID * @"EncryptionStatus" : An AIEncryptionStatus * @"AIAccount" : The AIAccount of this context * @"who" : The UID of the remote user * details_for_context ( ConnContext * context ) if ( ! context ) return nil ; if ( context -> recent_child ) context = context -> recent_child ; NSDictionary * securityDetailsDict ; Fingerprint * fprint = context -> active_fingerprint ; if ( ! fprint || ! ( fprint -> fingerprint )) return nil ; TrustLevel level = otrg_plugin_context_to_trust ( context ); AIEncryptionStatus encryptionStatus ; encryptionStatus = EncryptionStatus_None ; encryptionStatus = EncryptionStatus_Unverified ; encryptionStatus = EncryptionStatus_Verified ; encryptionStatus = EncryptionStatus_Finished ; char our_hash [ OTRL_PRIVKEY_FPRINT_HUMAN_LEN ], their_hash [ OTRL_PRIVKEY_FPRINT_HUMAN_LEN ]; otrl_privkey_fingerprint ( otrg_get_userstate (), our_hash , context -> accountname , context -> protocol ); otrl_privkey_hash_to_human ( their_hash , fprint -> fingerprint ); unsigned char * sessionid ; BOOL sess1_outgoing = ( context -> sessionid_half == OTRL_SESSIONID_FIRST_HALF_BOLD ); size_t idhalflen = ( context -> sessionid_len ) / 2 ; NSMutableString * sess1 , * sess2 ; sess1 = [[[ NSMutableString alloc ] initWithCapacity : 21 ] autorelease ]; sess2 = [[[ NSMutableString alloc ] initWithCapacity : 21 ] autorelease ]; /* Make a human-readable version of the sessionid (in two parts) */ sessionid = context -> sessionid ; for ( i = 0 ; i < idhalflen ; i ++ ){ [ sess1 appendFormat : @"%02x" , sessionid [ i ]]; [ sess2 appendFormat : @"%02x" , sessionid [ i + idhalflen ]]; account = [ adium . accountController accountWithInternalObjectID : [ NSString stringWithUTF8String : context -> accountname ]]; securityDetailsDict = @{ @"Their Fingerprint" : [ NSString stringWithUTF8String : their_hash ], @"Our Fingerprint" : [ NSString stringWithUTF8String : our_hash ], @"EncryptionStatus" : @( encryptionStatus ) , @"who" : [ NSString stringWithUTF8String : context -> username ], ( sess1_outgoing ? @"Outgoing SessionID" : @"Incoming SessionID" ) : sess1 , ( sess1_outgoing ? @"Incoming SessionID" : @"Outgoing SessionID" ) : sess2 } ; AILog ( @"Security details: %@" , securityDetailsDict ); return securityDetailsDict ; accountFromAccountID ( const char * accountID ) return [ adium . accountController accountWithInternalObjectID : [ NSString stringWithUTF8String : accountID ]]; serviceFromServiceID ( const char * serviceID ) return [ adium . accountController serviceWithUniqueID : [ NSString stringWithUTF8String : serviceID ]]; contactFromInfo ( const char * accountID , const char * serviceID , const char * username ) return [ adium . contactController contactWithService : serviceFromServiceID ( serviceID ) account : accountFromAccountID ( accountID ) UID :[ NSString stringWithUTF8String : username ]]; contactForContext ( ConnContext * context ) return contactFromInfo ( context -> accountname , context -> protocol , context -> username ); chatForContext ( ConnContext * context ) AIListContact * listContact = contactForContext ( context ); AIChat * chat = [ adium . chatController existingChatWithContact : listContact ]; chat = [ adium . chatController chatWithContact : listContact ]; policyForContact ( AIListContact * contact ) OtrlPolicy policy = OTRL_POLICY_MANUAL_AND_RESPOND_TO_WHITESPACE ; AIEncryptedChatPreference pref = contact . encryptedChatPreferences ; case EncryptedChat_Never : policy = OTRL_POLICY_NEVER ; case EncryptedChat_Manually : case EncryptedChat_Default : policy = OTRL_POLICY_MANUAL_AND_RESPOND_TO_WHITESPACE ; case EncryptedChat_Automatically : policy = OTRL_POLICY_OPPORTUNISTIC ; case EncryptedChat_RejectUnencryptedMessages : policy = OTRL_POLICY_ALWAYS ; //Return the ConnContext for a Conversation, or NULL if none exists contextForChat ( AIChat * chat ) const char * username , * accountname , * proto ; /* Do nothing if this isn't an IM conversation */ if ( chat . isGroupChat ) return NULL ; accountname = [ account . internalObjectID UTF8String ]; proto = [ account . service . serviceCodeUniqueID UTF8String ]; username = [ chat . listObject . UID UTF8String ]; context = otrl_context_find ( otrg_plugin_userstate , username , accountname , proto , OTRL_INSTAG_MASTER , TRUE , NULL , AILogWithSignature ( @"%@ -> %p" , chat , context ); /* What level of trust do we have in the privacy of this ConnContext? */ otrg_plugin_context_to_trust ( ConnContext * context ) TrustLevel level = TRUST_NOT_PRIVATE ; if ( context && context -> msgstate == OTRL_MSGSTATE_ENCRYPTED ) { if ( context -> active_fingerprint -> trust && context -> active_fingerprint -> trust [ 0 ] != '\0' ) { level = TRUST_UNVERIFIED ; } else if ( context && context -> msgstate == OTRL_MSGSTATE_FINISHED ) { #pragma mark Implementations of the app ops /* Return the OTR policy for the given context. */ policy_cb ( void * opdata , ConnContext * context ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; OtrlPolicy ret = policyForContact ( contactForContext ( context )); /* Asynchronously generate a private key for the given accountname/protocol */ otrg_plugin_create_privkey ( const char * accountname , const char * protocol ) static BOOL alreadyGenerating = FALSE ; static dispatch_queue_t keyGenerationQueue = NULL ; static dispatch_once_t onceToken ; dispatch_once ( & onceToken , ^ { keyGenerationQueue = dispatch_queue_create ( "im.adium.OTR.KeyGenerationQueue" , NULL ); AILogWithSignature ( @"A key generation is already running. Canceling" ); otrl_privkey_generate_start ( otrg_get_userstate (), accountname , protocol , & newkeyp ); alreadyGenerating = TRUE ; dispatch_async ( keyGenerationQueue , ^ { AILogWithSignature ( @"Generating a new private key" ); otrl_privkey_generate_calculate ( newkeyp ); dispatch_sync ( dispatch_get_main_queue (), ^ { otrl_privkey_generate_finish ( otrg_get_userstate (), newkeyp , PRIVKEY_PATH ); otrg_ui_update_keylist (); AILogWithSignature ( @"Done." ); alreadyGenerating = FALSE ; /* Create a private key for the given accountname/protocol if create_privkey_cb ( void * opdata , const char * accountname , const char * protocol ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; otrg_plugin_create_privkey ( accountname , protocol ); /* Report whether you think the given user is online. Return 1 if * you think he is, 0 if you think he isn't, -1 if you're not sure. * If you return 1, messages such as heartbeats or other * notifications may be sent to the user, which could result in "not * logged in" errors if you're wrong. */ is_logged_in_cb ( void * opdata , const char * accountname , const char * protocol , const char * recipient ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; AIListContact * contact = contactFromInfo ( accountname , protocol , recipient ); if ([ contact statusSummary ] == AIUnknownStatus ) ret = ( contact . online ? 1 : 0 ); /* Send the given IM to the given recipient from the given * accountname/protocol. */ inject_message_cb ( void * opdata , const char * accountname , const char * protocol , const char * recipient , const char * message ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; [ adium . contentController sendRawMessage : [ NSString stringWithUTF8String : message ] toContact : contactFromInfo ( accountname , protocol , recipient )]; /* When the list of ConnContexts changes (including a change in * state), this is called so the UI can be updated. */ update_context_list_cb ( void * opdata ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; otrg_ui_update_keylist (); /* Return a newly allocated string containing a human-friendly * representation for the given account */ account_display_name_cb ( void * opdata , const char * accountname , const char * protocol ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; const char * ret = strdup ([[ accountFromAccountID ( accountname ) formattedUID ] UTF8String ]); /* Deallocate a string returned by account_name */ account_display_name_free_cb ( void * opdata , const char * account_display_name ) if ( account_display_name ) free (( char * ) account_display_name ); /* A new fingerprint for the given user has been received. */ new_fingerprint_cb ( void * opdata , OtrlUserState us , const char * accountname , const char * protocol , const char * username , unsigned char fingerprint [ 20 ]) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; context = otrl_context_find ( us , username , accountname , protocol , OTRL_INSTAG_RECENT , 0 , NULL , NULL , NULL ); if ( context == NULL /* || context->msgstate != OTRL_MSGSTATE_ENCRYPTED*/ ) { NSLog ( @"otrg_adium_dialog_unknown_fingerprint: Ack!" ); /* The list of known fingerprints has changed. Write them to disk. */ write_fingerprints_cb ( void * opdata ) otrg_plugin_write_fingerprints (); /* A ConnContext has entered a secure state. Refresh the chat and the fingerprint list. */ gone_secure_cb ( void * opdata , ConnContext * context ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; update_security_details_for_context ( context ); otrg_ui_update_fingerprint (); /* A ConnContext has left a secure state. */ gone_insecure_cb ( void * opdata , ConnContext * context ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; update_security_details_for_context ( context ); otrg_ui_update_fingerprint (); /* We have completed an authentication, using the D-H keys we * already knew. is_reply indicates whether we initiated the AKE. */ still_secure_cb ( void * opdata , ConnContext * context , int is_reply ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; AILog ( @"Still secure..." ); * @brief Find the maximum message size supported by this protocol. * This method is called whenever a message is about to be sent with * fragmentation enabled. The return value is checked against the size of * the message to be sent to determine whether fragmentation is necessary. * Setting max_message_size to NULL will disable the fragmentation of all * sent messages; returning 0 from this callback will disable fragmentation * of a particular message. The latter is useful, for example, for * protocols like XMPP (Jabber) that do not require fragmentation at all. max_message_size_cb ( void * opdata , ConnContext * context ) NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc ] init ]; AIChat * chat = chatForContext ( context ); /* Values from https://otr.cypherpunks.ca/UPGRADING-libotr-3.1.0.txt */ static NSDictionary * maxSizeByServiceClassDict = nil ; static dispatch_once_t onceToken ; dispatch_once ( & onceToken , ^ { maxSizeByServiceClassDict = [ @{ @"AIM-compatible" : @( 2343 ) , @"IRC" : @( 417 ) } retain ]; /* This will return 0 if we don't know (unknown protocol) or don't need it (Jabber), * which will disable fragmentation. int ret = [[ maxSizeByServiceClassDict objectForKey : chat . account . service . serviceClass ] intValue ]; /* Create a string describing an error message event. */ error_message_cb ( void * opdata , ConnContext * context , OtrlErrorCode err_code ) NSString * errorMessage = nil ; case OTRL_ERRCODE_ENCRYPTION_ERROR : errorMessage = AILocalizedStringFromTableInBundle ( @"An error occured while encrypting a message" , nil , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ); case OTRL_ERRCODE_MSG_NOT_IN_PRIVATE : errorMessage = AILocalizedStringFromTableInBundle ( @"Sent encrypted message to somebody who is not in a mutual OTR session" , nil , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ); case OTRL_ERRCODE_MSG_UNREADABLE : errorMessage = AILocalizedStringFromTableInBundle ( @"Sent an unreadable encrypted message" , nil , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ); case OTRL_ERRCODE_MSG_MALFORMED : errorMessage = AILocalizedStringFromTableInBundle ( @"Message sent is malformed" , nil , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ); const char * message_str = strdup ([ errorMessage UTF8String ]); /* Free a string allocated by error_message_cb. */ error_message_free_cb ( void * opdata , const char * err_msg ) if ( err_msg ) free (( char * ) err_msg ); /* Translate "[resent]" to the sender's own localization. */ resent_msg_prefix_cb ( void * opdata , ConnContext * context ) const char * prefix_str = strdup ([ AILocalizedStringFromTableInBundle ( @"[resent]" , @"Prefix used by OTR for resent messages" , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ) UTF8String ]); /* Free the string allocated by resent_msg_prefix_cb. */ resent_msg_prefix_free_cb ( void * opdata , const char * prefix ) if ( prefix ) free (( char * ) prefix ); /* Create a timer for libotr to clean up. The timer doesn't need to be * exact, so we give it a 1 sec leeway. */ timer_control_cb ( void * opdata , unsigned int interval ) { static dispatch_source_t timer = NULL ; dispatch_source_cancel ( timer ); timer = dispatch_source_create ( DISPATCH_SOURCE_TYPE_TIMER , 0 , 0 , dispatch_get_main_queue ()); dispatch_source_set_timer ( timer , dispatch_time ( DISPATCH_TIME_NOW , interval * NSEC_PER_SEC ), interval * NSEC_PER_SEC , NSEC_PER_SEC ); dispatch_source_set_event_handler ( timer , ^ { otrl_message_poll ( otrg_plugin_userstate , & ui_ops , opdata ); handle_msg_event_cb ( void * opdata , OtrlMessageEvent msg_event , ConnContext * context , const char * message , gcry_error_t err ) AILogWithSignature ( @"Something happened in this conversation: %d %s" , msg_event , message ); AIListContact * listContact = contactForContext ( context ); AIChat * chat = chatForContext ( context ); case OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED : if ( ! chat ) chat = [ adium . chatController chatWithContact : listContact ]; AIContentMessage * messageObject = [ AIContentMessage messageInChat : chat message :[ AIHTMLDecoder decodeHTML : [ AILocalizedStringFromTableInBundle ( @"The following message was <b>not encrypted</b>: " , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ) stringByAppendingString :[ NSString stringWithUTF8String : message ]]] [ adium . contentController receiveContentObject : messageObject ]; case OTRL_MSGEVENT_RCVDMSG_FOR_OTHER_INSTANCE : AILogWithSignature ( @"Received an OTR message for a different instance. We will silently ignore it: %s" , message ); case OTRL_MSGEVENT_LOG_HEARTBEAT_RCVD : case OTRL_MSGEVENT_LOG_HEARTBEAT_SENT : AILogWithSignature ( @"I'm still alive" ); case OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED : case OTRL_MSGEVENT_RCVDMSG_MALFORMED : case OTRL_MSGEVENT_RCVDMSG_NOT_IN_PRIVATE : case OTRL_MSGEVENT_RCVDMSG_UNREADABLE : { NSString * localizedMessage = [ NSString stringWithFormat : AILocalizedStringFromTableInBundle ( @"An encrypted message from %@ could not be decrypted." , @"libotr error message" , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ), listContact . UID ]; if ( ! chat ) chat = [ adium . chatController chatWithContact : listContact ]; [ adium . contentController displayEvent : [[ AIHTMLDecoder decodeHTML : localizedMessage ] string ] case OTRL_MSGEVENT_CONNECTION_ENDED : { NSString * localizedMessage = [ NSString stringWithFormat : AILocalizedStringFromTableInBundle ( @"Your message was not sent. %@ is no longer using encryption; you should cancel or refresh encryption on your side." , @"libotr error message" , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], @"Message when the remote contact cancels his half of an encrypted conversation. %@ will be a name." ), listContact . UID ]; if ( ! chat ) chat = [ adium . chatController chatWithContact : listContact ]; [ adium . contentController displayEvent : [[ AIHTMLDecoder decodeHTML : localizedMessage ] string ] /* Create an instag for this account. */ create_instag_cb ( void * opdata , const char * accountname , const char * protocol ) otrl_instag_generate ( otrg_plugin_userstate , INSTAG_PATH , accountname , protocol ); /* Something related to Socialist Millionaire Protocol happened. Handle it. */ handle_smp_event_cb ( void * opdata , OtrlSMPEvent smp_event , ConnContext * context , unsigned short progress_percent , char * question ) AIListContact * listContact = contactForContext ( context ); AIChat * chat = chatForContext ( context ); if ( ! chat ) chat = [ adium . chatController chatWithContact : listContact ]; case OTRL_SMPEVENT_ASK_FOR_ANSWER : { AIOTRSMPSecretAnswerWindowController * questionController = [[ AIOTRSMPSecretAnswerWindowController alloc ] initWithQuestion :[ NSString stringWithUTF8String : question ] completionHandler : ^ ( NSData * answer , NSString * _question ){ if ( context != contextForChat ( chat )) { AILogWithSignature ( @"Something's wrong: %p != %p. Did the conversation close before you sent the secret question?" , context , contextForChat ( chat )); otrl_message_abort_smp ( otrg_get_userstate (), & ui_ops , opdata , context ); otrl_message_respond_smp ( otrg_get_userstate (), & ui_ops , opdata , context , [ answer bytes ], [ answer length ]); [ questionController showWindow : nil ]; [ questionController . window orderFront : nil ]; [ adium . contentController displayEvent : [ NSString stringWithFormat : AILocalizedStringFromTableInBundle ( @"%@ has sent you a secret question and is awaiting your answer to verify your identity." , nil , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ), case OTRL_SMPEVENT_ASK_FOR_SECRET : { AIOTRSMPSharedSecretWindowController * questionController = [[ AIOTRSMPSharedSecretWindowController alloc ] completionHandler : ^ ( NSData * answer ){ if ( context != contextForChat ( chat )) { AILogWithSignature ( @"Something's wrong: %p != %p. Did the conversation close before you sent the secret question?" , context , contextForChat ( chat )); otrl_message_respond_smp ( otrg_get_userstate (), & ui_ops , opdata , context , [ answer bytes ], [ answer length ]); [ questionController showWindow : nil ]; [ questionController . window orderFront : nil ]; [ adium . contentController displayEvent : [ NSString stringWithFormat : AILocalizedStringFromTableInBundle ( @"%@ has requested to compare your shared secret to verify your identity." , nil , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ), case OTRL_SMPEVENT_CHEATED : case OTRL_SMPEVENT_ERROR : case OTRL_SMPEVENT_FAILURE : case OTRL_SMPEVENT_ABORT : { NSString * localizedMessage = AILocalizedStringFromTableInBundle ( @"The secret question was <b>not</b> answered correctly. You might be talking to an imposter." , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ); [ adium . contentController displayEvent : localizedMessage case OTRL_SMPEVENT_SUCCESS : { NSString * localizedMessage = AILocalizedStringFromTableInBundle ( @"The secret question was answered correctly." , [ NSBundle bundleForClass : [ AdiumOTREncryption class ]], nil ); [ adium . contentController displayEvent : localizedMessage update_security_details_for_context ( context ); otrg_plugin_write_fingerprints (); otrg_ui_update_keylist (); static OtrlMessageAppOps ui_ops = { account_display_name_free_cb , NULL /* received_symkey */ , resent_msg_prefix_free_cb , #pragma mark Input/output of messages between Adium and libotr - ( void ) willSendContentMessage: ( AIContentMessage * ) inContentMessage const char * originalMessage = [[ inContentMessage encodedMessage ] UTF8String ]; AIAccount * account = ( AIAccount * )[ inContentMessage source ]; const char * accountname = [ account . internalObjectID UTF8String ]; const char * protocol = [ account . service . serviceCodeUniqueID UTF8String ]; const char * username = [[[ inContentMessage destination ] UID ] UTF8String ]; char * fullOutgoingMessage = NULL ; if ( ! username || ! originalMessage ) err = otrl_message_sending ( otrg_plugin_userstate , & ui_ops , /* opData */ NULL , accountname , protocol , username , OTRL_INSTAG_RECENT , originalMessage , /* tlvs */ NULL , & fullOutgoingMessage , OTRL_FRAGMENT_SEND_ALL_BUT_LAST , NULL , /* add_appdata cb */ NULL , /* appdata */ NULL ); if ( err && fullOutgoingMessage == NULL ) { //Be *sure* not to send out plaintext [ inContentMessage setEncodedMessage : nil ]; } else if ( fullOutgoingMessage ) { //This new message is what should be sent to the remote contact [ inContentMessage setEncodedMessage : [ NSString stringWithUTF8String : fullOutgoingMessage ]]; //We're now done with the messages allocated by OTR otrl_message_free ( fullOutgoingMessage ); - ( NSString * ) decryptIncomingMessage: ( NSString * ) inString fromContact: ( AIListContact * ) inListContact onAccount: ( AIAccount * ) inAccount NSString * decryptedMessage = nil ; const char * message = [ inString UTF8String ]; const char * username = [ inListContact . UID UTF8String ]; const char * accountname = [ inAccount . internalObjectID UTF8String ]; const char * protocol = [ inAccount . service . serviceCodeUniqueID UTF8String ]; /* If newMessage is set to non-NULL and res is 0, use newMessage. * If newMessage is set to non-NULL and res is not 0, display nothing as this was an OTR message * If newMessage is set to NULL and res is 0, use message res = otrl_message_receiving ( otrg_plugin_userstate , & ui_ops , NULL , accountname , protocol , username , message , & newMessage , & tlvs , NULL , NULL , NULL ); if ( ! newMessage && ! res ) { //Use the original mesage; this was not an OTR-related message decryptedMessage = inString ; AILogWithSignature ( @"Not OTR-related message for decryption." ); } else if ( newMessage && ! res ) { //We decryped an OTR-encrypted message decryptedMessage = [ NSString stringWithUTF8String : newMessage ]; AILogWithSignature ( @"Decrypted an OTR message." ); } else /* (newMessage && res) */ { //This was an OTR protocol message AILogWithSignature ( @"Skipping an OTR protocol message." ); otrl_message_free ( newMessage ); - ( void ) requestSecureOTRMessaging: ( BOOL ) inSecureMessaging inChat: ( AIChat * ) inChat send_default_query_to_chat ( inChat ); disconnect_from_chat ( inChat ); - ( void ) promptToVerifyEncryptionIdentityInChat: ( AIChat * ) inChat ConnContext * context = contextForChat ( inChat ); NSDictionary * responseInfo = details_for_context ( context );; [ ESOTRUnknownFingerprintController showVerifyFingerprintPromptWithResponseInfo : responseInfo ]; - ( void ) questionVerifyEncryptionIdentityInChat: ( AIChat * ) inChat ConnContext * context = contextForChat ( inChat ); if ( context -> recent_child ) context = context -> recent_child ; AIOTRSMPSecretAnswerWindowController * windowController = [[ AIOTRSMPSecretAnswerWindowController alloc ] completionHandler : ^ ( NSData * answer , NSString * question ) { if ( context != contextForChat ( inChat )) { AILogWithSignature ( @"Something's wrong: %p != %p. Did the conversation close before you sent the secret question?" , context , contextForChat ( inChat )); otrl_message_initiate_smp_q ( otrg_get_userstate (), ( const char * )[ question UTF8String ], [ adium . contentController displayEvent : [ NSString stringWithFormat : AILocalizedString ( @"You have asked %@ a secret question to verify their identity. Awaiting answer..." , nil ), inChat . listObject . displayName ] [ windowController showWindow : nil ]; [ windowController . window orderFront : nil ]; - ( void ) sharedVerifyEncryptionIdentityInChat: ( AIChat * ) inChat ConnContext * context = contextForChat ( inChat ); if ( context -> recent_child ) context = context -> recent_child ; AIOTRSMPSharedSecretWindowController * windowController = [[ AIOTRSMPSharedSecretWindowController alloc ] initFrom : inChat . listObject completionHandler : ^ ( NSData * answer ) { if ( context != contextForChat ( inChat )) { AILogWithSignature ( @"Something's wrong: %p != %p. Did the conversation close before you sent the secret question?" , context , contextForChat ( inChat )); otrl_message_initiate_smp ( otrg_get_userstate (), [ adium . contentController displayEvent : [ NSString stringWithFormat : AILocalizedString ( @"You have asked %@ to compare your shared secret to verify their identity. Awaiting answer..." , nil ), inChat . listObject . displayName ] [ windowController showWindow : nil ]; [ windowController . window orderFront : nil ]; * @brief Adium will begin terminating * Send the OTRL_TLV_DISCONNECTED packets when we're about to quit before we disconnect - ( void ) adiumWillTerminate: ( NSNotification * ) inNotification ConnContext * context = otrg_plugin_userstate -> context_root ; ConnContext * next = context -> next ; if ( context -> msgstate == OTRL_MSGSTATE_ENCRYPTED && context -> protocol_version > 1 ) { disconnect_from_context ( context ); * @brief A chat notification was posted after which we should update our security details * @param inNotification A notification whose object is the AIChat in question - ( void ) updateSecurityDetails: ( NSNotification * ) inNotification AILog ( @"Updating security details for %@" ,[ inNotification object ]); AIChat * chat = [ inNotification object ]; ConnContext * context = contextForChat ( chat ); if ( context ) update_security_details_for_context ( context ); void update_security_details_for_context ( ConnContext * context ) AIChat * chat = chatForContext ( context ); [ adiumOTREncryption setSecurityDetails : details_for_context ( context ) - ( void ) setSecurityDetails: ( NSDictionary * ) securityDetailsDict forChat: ( AIChat * ) inChat NSMutableDictionary * fullSecurityDetailsDict ; if ( securityDetailsDict ) { NSString * format , * description ; fullSecurityDetailsDict = [[ securityDetailsDict mutableCopy ] autorelease ]; /* Encrypted by Off-the-Record Messaging * Fingerprint for TekJew: * Secure ID for this session: * Incoming: <Incoming SessionID> * Outgoing: <Outgoing SessionID> format = [ @"%@ \n\n " stringByAppendingString : AILocalizedString ( @"Fingerprint for %@:" , "Fingerprint for <name>:" )]; format = [ format stringByAppendingString : @" \n %@ \n\n %@ \n %@ %@ \n %@ %@" ]; description = [ NSString stringWithFormat : format , AILocalizedString ( @"Encrypted by Off-the-Record Messaging" , nil ), [[ inChat listObject ] formattedUID ], [ securityDetailsDict objectForKey : @"Their Fingerprint" ], AILocalizedString ( @"Secure ID for this session:" , nil ), AILocalizedString ( @"Incoming:" , "This is shown before the Off-the-Record Session ID (a series of numbers and letters) sent by the other party with whom you are having an encrypted chat." ), [ securityDetailsDict objectForKey : @"Incoming SessionID" ], AILocalizedString ( @"Outgoing:" , "This is shown before the Off-the-Record Session ID (a series of numbers and letters) sent by you to the other party with whom you are having an encrypted chat." ), [ securityDetailsDict objectForKey : @"Outgoing SessionID" ], [ fullSecurityDetailsDict setObject : description fullSecurityDetailsDict = nil ; NSInteger oldEncryptionStatus = [[[ inChat securityDetails ] objectForKey : @"EncryptionStatus" ] integerValue ]; [ inChat setSecurityDetails : fullSecurityDetailsDict ]; NSInteger newEncryptionStatus = [[ securityDetailsDict objectForKey : @"EncryptionStatus" ] integerValue ]; if ( newEncryptionStatus == EncryptionStatus_Unverified && oldEncryptionStatus != EncryptionStatus_Unverified ) { AIOTRTopBarUnverifiedContactController * warningController = [[ AIOTRTopBarUnverifiedContactController alloc ] init ]; AIMessageViewController * mvc = [[ inChat chatContainer ] messageViewController ]; [ mvc addTopBarController : warningController ]; send_default_query_to_chat ( AIChat * inChat ) //Note that we pass a name for display, not internal usage char * msg = otrl_proto_default_query_msg ([ inChat . account . formattedUID UTF8String ], policyForContact ([ inChat listObject ])); [ adium . contentController sendRawMessage : [ NSString stringWithUTF8String : ( msg ? msg : "?OTRv2?" )] toContact :[ inChat listObject ]]; /* Disconnect a context, sending a notice to the other side, if disconnect_from_context ( ConnContext * context ) otrl_message_disconnect_all_instances ( otrg_plugin_userstate , & ui_ops , NULL , context -> accountname , context -> protocol , context -> username ); gone_insecure_cb ( NULL , context ); disconnect_from_chat ( AIChat * inChat ) disconnect_from_context ( contextForChat ( inChat )); /* Forget a fingerprint */ otrg_ui_forget_fingerprint ( Fingerprint * fingerprint ) /* Don't do anything with the active fingerprint if we're in the context = ( fingerprint ? fingerprint -> context : NULL ); if ( context && ( context -> msgstate == OTRL_MSGSTATE_ENCRYPTED && context -> active_fingerprint == fingerprint )) return ; otrl_context_forget_fingerprint ( fingerprint , 1 ); otrg_plugin_write_fingerprints (); otrg_plugin_write_fingerprints ( void ) otrl_privkey_write_fingerprints ( otrg_plugin_userstate , STORE_PATH ); otrg_ui_update_fingerprint (); otrg_ui_update_keylist ( void ) [ adiumOTREncryption prefsShouldUpdatePrivateKeyList ]; otrg_ui_update_fingerprint ( void ) [ adiumOTREncryption prefsShouldUpdateFingerprintsList ]; return otrg_plugin_userstate ; * @brief Call this function when our DSA key is updated; it will redraw the Encryption preferences item, if visible. - ( void ) prefsShouldUpdatePrivateKeyList [ OTRPrefs updatePrivateKeyList ]; * @brief Update the list of other users' fingerprints, if it's visible - ( void ) prefsShouldUpdateFingerprintsList [ OTRPrefs updateFingerprintsList ];