/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.plugin.otr; import java.net.*; import java.security.*; import java.util.*; import java.util.concurrent.*; import net.java.otr4j.*; import net.java.otr4j.crypto.*; import net.java.otr4j.session.*; import net.java.sip.communicator.plugin.otr.OtrContactManager.OtrContact; import net.java.sip.communicator.plugin.otr.authdialog.*; import net.java.sip.communicator.service.browserlauncher.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; import org.osgi.framework.*; /** * * @author George Politis * @author Lyubomir Marinov * @author Pawel Domas * @author Marin Dzhigarov * @author Danny van Heumen */ public class ScOtrEngineImpl implements ScOtrEngine, ChatLinkClickedListener, ServiceListener { private class ScOtrEngineHost implements OtrEngineHost { @Override public KeyPair getLocalKeyPair(SessionID sessionID) { AccountID accountID = OtrActivator.getAccountIDByUID(sessionID.getAccountID()); KeyPair keyPair = OtrActivator.scOtrKeyManager.loadKeyPair(accountID); if (keyPair == null) OtrActivator.scOtrKeyManager.generateKeyPair(accountID); return OtrActivator.scOtrKeyManager.loadKeyPair(accountID); } @Override public OtrPolicy getSessionPolicy(SessionID sessionID) { return getContactPolicy(getOtrContact(sessionID).contact); } @Override public void injectMessage(SessionID sessionID, String messageText) { OtrContact otrContact = getOtrContact(sessionID); Contact contact = otrContact.contact; ContactResource resource = null; if (contact.supportResources()) { Collection<ContactResource> resources = contact.getResources(); if (resources != null) { for (ContactResource r : resources) { if (r.equals(otrContact.resource)) { resource = r; break; } } } } OperationSetBasicInstantMessaging imOpSet = contact .getProtocolProvider() .getOperationSet( OperationSetBasicInstantMessaging.class); // This is a dirty way of detecting whether the injected message // contains HTML markup. If this is the case then we should create // the message with the appropriate content type so that the remote // party can properly display the HTML. // When otr4j injects QueryMessages it calls // OtrEngineHost.getFallbackMessage() which is currently the only // host method that uses HTML so we can simply check if the injected // message contains the string that getFallbackMessage() returns. String otrHtmlFallbackMessage = "<a href=\"http://en.wikipedia.org/wiki/Off-the-Record_Messaging\">"; String contentType = messageText.contains(otrHtmlFallbackMessage) ? OperationSetBasicInstantMessaging.HTML_MIME_TYPE : OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE; Message message = imOpSet.createMessage( messageText, contentType, OperationSetBasicInstantMessaging.DEFAULT_MIME_ENCODING, null); injectedMessageUIDs.add(message.getMessageUID()); imOpSet.sendInstantMessage(contact, resource, message); } @Override public void showError(SessionID sessionID, String err) { ScOtrEngineImpl.this.showError(sessionID, err); } public void showWarning(SessionID sessionID, String warn) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, warn, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); } @Override public void unreadableMessageReceived(SessionID sessionID) throws OtrException { OtrContact otrContact = getOtrContact(sessionID); String resourceName = otrContact.resource != null ? "/" + otrContact.resource.getResourceName() : ""; Contact contact = otrContact.contact; String error = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.unreadablemsgreceived", new String[] {contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, error, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); } @Override public void unencryptedMessageReceived(SessionID sessionID, String msg) throws OtrException { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; String warn = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.unencryptedmsgreceived"); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, warn, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); } @Override public void smpError(SessionID sessionID, int tlvType, boolean cheated) throws OtrException { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; logger.debug("SMP error occurred" + ". Contact: " + contact.getDisplayName() + ". TLV type: " + tlvType + ". Cheated: " + cheated); String error = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.smperror"); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, error, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); progressDialogMap.put(otrContact, progressDialog); } progressDialog.setProgressFail(); progressDialog.setVisible(true); } @Override public void smpAborted(SessionID sessionID) throws OtrException { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; Session session = otrEngine.getSession(sessionID); if (session.isSmpInProgress()) { String warn = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.smpaborted", new String[] {contact.getDisplayName()}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, warn, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); progressDialogMap.put(otrContact, progressDialog); } progressDialog.setProgressFail(); progressDialog.setVisible(true); } } @Override public void finishedSessionMessage(SessionID sessionID, String msgText) throws OtrException { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; String resourceName = otrContact.resource != null ? "/" + otrContact.resource.getResourceName() : ""; Contact contact = otrContact.contact; String error = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.sessionfinishederror", new String[] {msgText, contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, error, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); } @Override public void requireEncryptedMessage(SessionID sessionID, String msgText) throws OtrException { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; String error = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.requireencryption", new String[] {msgText}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, error, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); } @Override public byte[] getLocalFingerprintRaw(SessionID sessionID) { AccountID accountID = OtrActivator.getAccountIDByUID(sessionID.getAccountID()); return OtrActivator.scOtrKeyManager.getLocalFingerprintRaw(accountID); } @Override public void askForSecret( SessionID sessionID, InstanceTag receiverTag, String question) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; SmpAuthenticateBuddyDialog dialog = new SmpAuthenticateBuddyDialog( otrContact, receiverTag, question); dialog.setVisible(true); SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); progressDialogMap.put(otrContact, progressDialog); } progressDialog.init(); progressDialog.setVisible(true); } @Override public void verify( SessionID sessionID, String fingerprint, boolean approved) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; OtrActivator.scOtrKeyManager.verify(otrContact, fingerprint); SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); progressDialogMap.put(otrContact, progressDialog); } progressDialog.setProgressSuccess(); progressDialog.setVisible(true); } @Override public void unverify(SessionID sessionID, String fingerprint) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; OtrActivator.scOtrKeyManager.unverify(otrContact, fingerprint); SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(contact); progressDialogMap.put(otrContact, progressDialog); } progressDialog.setProgressFail(); progressDialog.setVisible(true); } @Override public String getReplyForUnreadableMessage(SessionID sessionID) { AccountID accountID = OtrActivator.getAccountIDByUID(sessionID.getAccountID()); return OtrActivator.resourceService.getI18NString( "plugin.otr.activator.unreadablemsgreply", new String[] {accountID.getDisplayName(), accountID.getDisplayName()}); } @Override public String getFallbackMessage(SessionID sessionID) { AccountID accountID = OtrActivator.getAccountIDByUID(sessionID.getAccountID()); return OtrActivator.resourceService.getI18NString( "plugin.otr.activator.fallbackmessage", new String[] {accountID.getDisplayName()}); } @Override public void multipleInstancesDetected(SessionID sessionID) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; String resourceName = otrContact.resource != null ? "/" + otrContact.resource.getResourceName() : ""; Contact contact = otrContact.contact; String message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.multipleinstancesdetected", new String[] {contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, message, OperationSetBasicInstantMessaging.HTML_MIME_TYPE); } @Override public void messageFromAnotherInstanceReceived(SessionID sessionID) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; String resourceName = otrContact.resource != null ? "/" + otrContact.resource.getResourceName() : ""; Contact contact = otrContact.contact; String message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.msgfromanotherinstance", new String[] {contact.getDisplayName() + resourceName}); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, message, OperationSetBasicInstantMessaging.HTML_MIME_TYPE); } /** * Provide fragmenter instructions according to the Instant Messaging * transport channel of the contact's protocol. */ @Override public FragmenterInstructions getFragmenterInstructions( final SessionID sessionID) { final OtrContact otrContact = getOtrContact(sessionID); final OperationSetBasicInstantMessagingTransport transport = otrContact.contact.getProtocolProvider().getOperationSet( OperationSetBasicInstantMessagingTransport.class); if (transport == null) { // There is no operation set for querying transport parameters. // Assuming transport capabilities are unlimited. if (logger.isDebugEnabled()) { logger.debug("No implementation of " + "BasicInstantMessagingTransport available. Assuming " + "OTR defaults for OTR fragmentation instructions."); } return null; } int messageSize = transport.getMaxMessageSize(otrContact.contact); if (messageSize == OperationSetBasicInstantMessagingTransport.UNLIMITED) { messageSize = FragmenterInstructions.UNLIMITED; } int numberOfMessages = transport.getMaxNumberOfMessages(otrContact.contact); if (numberOfMessages == OperationSetBasicInstantMessagingTransport.UNLIMITED) { numberOfMessages = FragmenterInstructions.UNLIMITED; } if (logger.isDebugEnabled()) { logger.debug("OTR fragmentation instructions for sending a " + "message to " + otrContact.contact.getDisplayName() + " (" + otrContact.contact.getAddress() + "). Maximum number of " + "messages: " + numberOfMessages + ", maximum message size: " + messageSize); } return new FragmenterInstructions(numberOfMessages, messageSize); } } /** * The max timeout period elapsed prior to establishing a TIMED_OUT session. */ private static final int SESSION_TIMEOUT = OtrActivator.configService.getInt( "net.java.sip.communicator.plugin.otr.SESSION_STATUS_TIMEOUT", 30000); /** * Manages the scheduling of TimerTasks that are used to set Contact's * ScSessionStatus (to TIMED_OUT) after a period of time. */ private ScSessionStatusScheduler scheduler = new ScSessionStatusScheduler(); /** * This mapping is used for taking care of keeping SessionStatus and * ScSessionStatus in sync for every Session object. */ private Map<SessionID, ScSessionStatus> scSessionStatusMap = new ConcurrentHashMap<SessionID, ScSessionStatus>(); private static final Map<ScSessionID, OtrContact> contactsMap = new Hashtable<ScSessionID, OtrContact>(); private static final Map<OtrContact, SmpProgressDialog> progressDialogMap = new ConcurrentHashMap<OtrContact, SmpProgressDialog>(); public static OtrContact getOtrContact(SessionID sessionID) { return contactsMap.get(new ScSessionID(sessionID)); } /** * Returns the <tt>ScSessionID</tt> for given <tt>UUID</tt>. * @param guid the <tt>UUID</tt> identifying <tt>ScSessionID</tt>. * @return the <tt>ScSessionID</tt> for given <tt>UUID</tt> or <tt>null</tt> * if no matching session found. */ public static ScSessionID getScSessionForGuid(UUID guid) { for(ScSessionID scSessionID : contactsMap.keySet()) { if(scSessionID.getGUID().equals(guid)) { return scSessionID; } } return null; } public static SessionID getSessionID(OtrContact otrContact) { ProtocolProviderService pps = otrContact.contact.getProtocolProvider(); String resourceName = otrContact.resource != null ? "/" + otrContact.resource.getResourceName() : ""; SessionID sessionID = new SessionID( pps.getAccountID().getAccountUniqueID(), otrContact.contact.getAddress() + resourceName, pps.getProtocolName()); synchronized (contactsMap) { if(contactsMap.containsKey(new ScSessionID(sessionID))) return sessionID; ScSessionID scSessionID = new ScSessionID(sessionID); contactsMap.put(scSessionID, otrContact); } return sessionID; } private final OtrConfigurator configurator = new OtrConfigurator(); private final List<String> injectedMessageUIDs = new Vector<String>(); private final List<ScOtrEngineListener> listeners = new Vector<ScOtrEngineListener>(); /** * The logger */ private final Logger logger = Logger.getLogger(ScOtrEngineImpl.class); private final OtrEngineHost otrEngineHost = new ScOtrEngineHost(); private final OtrSessionManager otrEngine; public ScOtrEngineImpl() { otrEngine = new OtrSessionManagerImpl(otrEngineHost); // Clears the map after previous instance // This is required because of OSGi restarts in the same VM on Android contactsMap.clear(); scSessionStatusMap.clear(); this.otrEngine.addOtrEngineListener(new OtrEngineListener() { @Override public void sessionStatusChanged(SessionID sessionID) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; String resourceName = otrContact.resource != null ? "/" + otrContact.resource.getResourceName() : ""; Contact contact = otrContact.contact; // Cancels any scheduled tasks that will change the // ScSessionStatus for this Contact scheduler.cancel(otrContact); ScSessionStatus scSessionStatus = getSessionStatus(otrContact); String message = ""; final Session session = otrEngine.getSession(sessionID); switch (session.getSessionStatus()) { case ENCRYPTED: scSessionStatus = ScSessionStatus.ENCRYPTED; scSessionStatusMap.put(sessionID, scSessionStatus); PublicKey remotePubKey = session.getRemotePublicKey(); String remoteFingerprint = null; try { remoteFingerprint = new OtrCryptoEngineImpl(). getFingerprint(remotePubKey); } catch (OtrCryptoException e) { logger.debug( "Could not get the fingerprint from the " + "public key of contact: " + contact); } List<String> allFingerprintsOfContact = OtrActivator.scOtrKeyManager. getAllRemoteFingerprints(contact); if (allFingerprintsOfContact != null) { if (!allFingerprintsOfContact.contains( remoteFingerprint)) { OtrActivator.scOtrKeyManager.saveFingerprint( contact, remoteFingerprint); } } if (!OtrActivator.scOtrKeyManager.isVerified( contact, remoteFingerprint)) { OtrActivator.scOtrKeyManager.unverify( otrContact, remoteFingerprint); UUID sessionGuid = null; for(ScSessionID scSessionID : contactsMap.keySet()) { if(scSessionID.getSessionID().equals(sessionID)) { sessionGuid = scSessionID.getGUID(); break; } } OtrActivator.uiService.getChat(contact) .addChatLinkClickedListener(ScOtrEngineImpl.this); String unverifiedSessionWarning = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.unverifiedsessionwarning", new String[] { contact.getDisplayName() + resourceName, this.getClass().getName(), "AUTHENTIFICATION", sessionGuid.toString() }); OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, unverifiedSessionWarning, OperationSetBasicInstantMessaging.HTML_MIME_TYPE); } // show info whether history is on or off String otrAndHistoryMessage; if(!OtrActivator.getMessageHistoryService() .isHistoryLoggingEnabled() || !isHistoryLoggingEnabled(contact)) { otrAndHistoryMessage = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.historyoff", new String[]{ OtrActivator.resourceService .getSettingsString( "service.gui.APPLICATION_NAME"), this.getClass().getName(), "showHistoryPopupMenu" }); } else { otrAndHistoryMessage = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.historyon", new String[]{ OtrActivator.resourceService .getSettingsString( "service.gui.APPLICATION_NAME"), this.getClass().getName(), "showHistoryPopupMenu" }); } OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, otrAndHistoryMessage, OperationSetBasicInstantMessaging.HTML_MIME_TYPE); message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.multipleinstancesdetected", new String[] {contact.getDisplayName()}); if (contact.supportResources() && contact.getResources() != null && contact.getResources().size() > 1) OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, message, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); message = OtrActivator.resourceService.getI18NString( OtrActivator.scOtrKeyManager.isVerified( contact, remoteFingerprint) ? "plugin.otr.activator.sessionstared" : "plugin.otr.activator.unverifiedsessionstared", new String[] {contact.getDisplayName() + resourceName}); break; case FINISHED: scSessionStatus = ScSessionStatus.FINISHED; scSessionStatusMap.put(sessionID, scSessionStatus); message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.sessionfinished", new String[] {contact.getDisplayName() + resourceName}); break; case PLAINTEXT: scSessionStatus = ScSessionStatus.PLAINTEXT; scSessionStatusMap.put(sessionID, scSessionStatus); message = OtrActivator.resourceService.getI18NString( "plugin.otr.activator.sessionlost", new String[] {contact.getDisplayName() + resourceName}); break; } OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.SYSTEM_MESSAGE, message, OperationSetBasicInstantMessaging.HTML_MIME_TYPE); for (ScOtrEngineListener l : getListeners()) l.sessionStatusChanged(otrContact); } @Override public void multipleInstancesDetected(SessionID sessionID) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; for (ScOtrEngineListener l : getListeners()) l.multipleInstancesDetected(otrContact); } @Override public void outgoingSessionChanged(SessionID sessionID) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; for (ScOtrEngineListener l : getListeners()) l.outgoingSessionChanged(otrContact); } }); } /** * Checks whether history is enabled for the metacontact containing * the <tt>contact</tt>. * @param contact the contact to check. * @return whether chat logging is enabled while chatting * with <tt>contact</tt>. */ private boolean isHistoryLoggingEnabled(Contact contact) { MetaContact metaContact = OtrActivator .getContactListService().findMetaContactByContact(contact); if(metaContact != null) return OtrActivator.getMessageHistoryService() .isHistoryLoggingEnabled(metaContact.getMetaUID()); else return true; } @Override public void addListener(ScOtrEngineListener l) { synchronized (listeners) { if (!listeners.contains(l)) listeners.add(l); } } @Override public void chatLinkClicked(URI url) { String action = url.getPath(); if(action.equals("/AUTHENTIFICATION")) { UUID guid = UUID.fromString(url.getQuery()); if(guid == null) throw new RuntimeException( "No UUID found in OTR authenticate URL"); // Looks for registered action handler OtrActionHandler actionHandler = ServiceUtils.getService( OtrActivator.bundleContext, OtrActionHandler.class); if(actionHandler != null) { actionHandler.onAuthenticateLinkClicked(guid); } else { logger.error("No OtrActionHandler registered"); } } } @Override public void endSession(OtrContact otrContact) { SessionID sessionID = getSessionID(otrContact); try { setSessionStatus(otrContact, ScSessionStatus.PLAINTEXT); otrEngine.getSession(sessionID).endSession(); } catch (OtrException e) { showError(sessionID, e.getMessage()); } } @Override public OtrPolicy getContactPolicy(Contact contact) { ProtocolProviderService pps = contact.getProtocolProvider(); SessionID sessionID = new SessionID( pps.getAccountID().getAccountUniqueID(), contact.getAddress(), pps.getProtocolName()); int policy = this.configurator.getPropertyInt(sessionID + "contact_policy", -1); if (policy < 0) return getGlobalPolicy(); else return new OtrPolicyImpl(policy); } @Override public OtrPolicy getGlobalPolicy() { /* * SEND_WHITESPACE_TAG bit will be lowered until we stabilize the OTR. */ int defaultScOtrPolicy = OtrPolicy.OTRL_POLICY_DEFAULT & ~OtrPolicy.SEND_WHITESPACE_TAG; return new OtrPolicyImpl(this.configurator.getPropertyInt( "GLOBAL_POLICY", defaultScOtrPolicy)); } /** * Gets a copy of the list of <tt>ScOtrEngineListener</tt>s registered with * this instance which may safely be iterated without the risk of a * <tt>ConcurrentModificationException</tt>. * * @return a copy of the list of <tt>ScOtrEngineListener<tt>s registered * with this instance which may safely be iterated without the risk of a * <tt>ConcurrentModificationException</tt> */ private ScOtrEngineListener[] getListeners() { synchronized (listeners) { return listeners.toArray(new ScOtrEngineListener[listeners.size()]); } } /** * Manages the scheduling of TimerTasks that are used to set Contact's * ScSessionStatus after a period of time. * * @author Marin Dzhigarov */ private class ScSessionStatusScheduler { private final Timer timer = new Timer(); private final Map<OtrContact, TimerTask> tasks = new ConcurrentHashMap<OtrContact, TimerTask>(); public void scheduleScSessionStatusChange( final OtrContact otrContact, final ScSessionStatus status) { cancel(otrContact); TimerTask task = new TimerTask() { @Override public void run() { setSessionStatus(otrContact, status); } }; timer.schedule(task, SESSION_TIMEOUT); tasks.put(otrContact, task); } public void cancel(final OtrContact otrContact) { TimerTask task = tasks.get(otrContact); if (task != null) task.cancel(); tasks.remove(otrContact); } public void serviceChanged(ServiceEvent ev) { Object service = OtrActivator.bundleContext.getService( ev.getServiceReference()); if (!(service instanceof ProtocolProviderService)) return; if (ev.getType() == ServiceEvent.UNREGISTERING) { ProtocolProviderService provider = (ProtocolProviderService) service; Iterator<OtrContact> i = tasks.keySet().iterator(); while (i.hasNext()) { OtrContact otrContact = i.next(); if (provider.equals( otrContact.contact.getProtocolProvider())) { cancel(otrContact); i.remove(); } } } } } private void setSessionStatus(OtrContact contact, ScSessionStatus status) { scSessionStatusMap.put(getSessionID(contact), status); scheduler.cancel(contact); for (ScOtrEngineListener l : getListeners()) l.sessionStatusChanged(contact); } @Override public ScSessionStatus getSessionStatus(OtrContact contact) { SessionID sessionID = getSessionID(contact); SessionStatus sessionStatus = otrEngine.getSession(sessionID).getSessionStatus(); ScSessionStatus scSessionStatus = null; if (!scSessionStatusMap.containsKey(sessionID)) { switch (sessionStatus) { case PLAINTEXT: scSessionStatus = ScSessionStatus.PLAINTEXT; break; case ENCRYPTED: scSessionStatus = ScSessionStatus.ENCRYPTED; break; case FINISHED: scSessionStatus = ScSessionStatus.FINISHED; break; } scSessionStatusMap.put(sessionID, scSessionStatus); } return scSessionStatusMap.get(sessionID); } @Override public boolean isMessageUIDInjected(String mUID) { return injectedMessageUIDs.contains(mUID); } @Override public void launchHelp() { ServiceReference ref = OtrActivator.bundleContext .getServiceReference(BrowserLauncherService.class.getName()); if (ref == null) return; BrowserLauncherService service = (BrowserLauncherService) OtrActivator.bundleContext.getService(ref); service.openURL(OtrActivator.resourceService .getI18NString("plugin.otr.authbuddydialog.HELP_URI")); } @Override public void refreshSession(OtrContact otrContact) { SessionID sessionID = getSessionID(otrContact); try { otrEngine.getSession(sessionID).refreshSession(); } catch (OtrException e) { logger.error("Error refreshing session", e); showError(sessionID, e.getMessage()); } } @Override public void removeListener(ScOtrEngineListener l) { synchronized (listeners) { listeners.remove(l); } } /** * Cleans the contactsMap when <tt>ProtocolProviderService</tt> * gets unregistered. */ @Override public void serviceChanged(ServiceEvent ev) { Object service = OtrActivator.bundleContext.getService(ev.getServiceReference()); if (!(service instanceof ProtocolProviderService)) return; if (ev.getType() == ServiceEvent.UNREGISTERING) { if (logger.isDebugEnabled()) { logger.debug( "Unregistering a ProtocolProviderService, cleaning" + " OTR's ScSessionID to Contact map."); logger.debug( "Unregistering a ProtocolProviderService, cleaning" + " OTR's Contact to SpmProgressDialog map."); } ProtocolProviderService provider = (ProtocolProviderService) service; synchronized(contactsMap) { Iterator<OtrContact> i = contactsMap.values().iterator(); while (i.hasNext()) { OtrContact otrContact = i.next(); if (provider.equals( otrContact.contact.getProtocolProvider())) { scSessionStatusMap.remove(getSessionID(otrContact)); i.remove(); } } } Iterator<OtrContact> i = progressDialogMap.keySet().iterator(); while (i.hasNext()) { if (provider.equals(i.next().contact.getProtocolProvider())) i.remove(); } scheduler.serviceChanged(ev); } } @Override public void setContactPolicy(Contact contact, OtrPolicy policy) { ProtocolProviderService pps = contact.getProtocolProvider(); SessionID sessionID = new SessionID( pps.getAccountID().getAccountUniqueID(), contact.getAddress(), pps.getProtocolName()); String propertyID = sessionID + "contact_policy"; if (policy == null) this.configurator.removeProperty(propertyID); else this.configurator.setProperty(propertyID, policy.getPolicy()); for (ScOtrEngineListener l : getListeners()) l.contactPolicyChanged(contact); } @Override public void setGlobalPolicy(OtrPolicy policy) { if (policy == null) this.configurator.removeProperty("GLOBAL_POLICY"); else this.configurator.setProperty("GLOBAL_POLICY", policy.getPolicy()); for (ScOtrEngineListener l : getListeners()) l.globalPolicyChanged(); } public void showError(SessionID sessionID, String err) { OtrContact otrContact = getOtrContact(sessionID); if (otrContact == null) return; Contact contact = otrContact.contact; OtrActivator.uiService.getChat(contact).addMessage( contact.getDisplayName(), new Date(), Chat.ERROR_MESSAGE, err, OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE); } @Override public void startSession(OtrContact otrContact) { SessionID sessionID = getSessionID(otrContact); ScSessionStatus scSessionStatus = getSessionStatus(otrContact); scSessionStatus = ScSessionStatus.LOADING; scSessionStatusMap.put(sessionID, scSessionStatus); for (ScOtrEngineListener l : getListeners()) { l.sessionStatusChanged(otrContact); } scheduler.scheduleScSessionStatusChange( otrContact, ScSessionStatus.TIMED_OUT); try { otrEngine.getSession(sessionID).startSession(); } catch (OtrException e) { logger.error("Error starting session", e); showError(sessionID, e.getMessage()); } } @Override public String transformReceiving(OtrContact otrContact, String msgText) { SessionID sessionID = getSessionID(otrContact); try { return otrEngine.getSession(sessionID).transformReceiving(msgText); } catch (OtrException e) { logger.error("Error receiving the message", e); showError(sessionID, e.getMessage()); return null; } } @Override public String[] transformSending(OtrContact otrContact, String msgText) { SessionID sessionID = getSessionID(otrContact); try { return otrEngine.getSession(sessionID).transformSending(msgText); } catch (OtrException e) { logger.error("Error transforming the message", e); showError(sessionID, e.getMessage()); return null; } } private Session getSession(OtrContact contact) { SessionID sessionID = getSessionID(contact); return otrEngine.getSession(sessionID); } @Override public void initSmp(OtrContact otrContact, String question, String secret) { Session session = getSession(otrContact); try { session.initSmp(question, secret); SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(otrContact.contact); progressDialogMap.put(otrContact, progressDialog); } progressDialog.init(); progressDialog.setVisible(true); } catch (OtrException e) { logger.error("Error initializing SMP session with contact " + otrContact.contact.getDisplayName(), e); showError(session.getSessionID(), e.getMessage()); } } @Override public void respondSmp( OtrContact otrContact, InstanceTag receiverTag, String question, String secret) { Session session = getSession(otrContact); try { session.respondSmp(receiverTag, question, secret); SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(otrContact.contact); progressDialogMap.put(otrContact, progressDialog); } progressDialog.incrementProgress(); progressDialog.setVisible(true); } catch (OtrException e) { logger.error( "Error occured when sending SMP response to contact " + otrContact.contact.getDisplayName(), e); showError(session.getSessionID(), e.getMessage()); } } @Override public void abortSmp(OtrContact otrContact) { Session session = getSession(otrContact); try { session.abortSmp(); SmpProgressDialog progressDialog = progressDialogMap.get(otrContact); if (progressDialog == null) { progressDialog = new SmpProgressDialog(otrContact.contact); progressDialogMap.put(otrContact, progressDialog); } progressDialog.dispose(); } catch (OtrException e) { logger.error("Error aborting SMP session with contact " + otrContact.contact.getDisplayName(), e); showError(session.getSessionID(), e.getMessage()); } } @Override public PublicKey getRemotePublicKey(OtrContact otrContact) { if (otrContact == null) return null; Session session = getSession(otrContact); return session.getRemotePublicKey(); } @Override public List<Session> getSessionInstances(OtrContact otrContact) { if (otrContact == null) return Collections.emptyList(); return getSession(otrContact).getInstances(); } @Override public boolean setOutgoingSession(OtrContact contact, InstanceTag tag) { if (contact == null) return false; Session session = getSession(contact); scSessionStatusMap.remove(session.getSessionID()); return session.setOutgoingInstance(tag); } @Override public Session getOutgoingSession(OtrContact contact) { if (contact == null) return null; SessionID sessionID = getSessionID(contact); return otrEngine.getSession(sessionID).getOutgoingInstance(); } }