/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package net.java.sip.communicator.impl.protocol.msn; import java.net.*; import java.nio.channels.*; import net.java.sip.communicator.service.dns.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import net.sf.jml.*; import net.sf.jml.event.*; import net.sf.jml.exception.*; import net.sf.jml.impl.*; /** * An implementation of the protocol provider service over the Msn protocol * * @author Damian Minkov * @author Lubomir Marinov */ public class ProtocolProviderServiceMsnImpl extends AbstractProtocolProviderService { /** * Logger of this class */ private static final Logger logger = Logger.getLogger(ProtocolProviderServiceMsnImpl.class); /** * The lib messenger. */ private MsnMessenger messenger = null; /** * We use this to lock access to initialization. */ private final Object initializationLock = new Object(); /** * The identifier of the account that this provider represents. */ private AccountID accountID = null; /** * Used when we need to re-register */ private SecurityAuthority authority = null; /** * Operation set for persistent presence. */ private OperationSetPersistentPresenceMsnImpl persistentPresence = null; /** * Operation set for typing notifications. */ private OperationSetTypingNotificationsMsnImpl typingNotifications = null; /** * The icon corresponding to the msn protocol. */ private final ProtocolIconMsnImpl msnIcon = new ProtocolIconMsnImpl(); /** * The indicator which determines whether * {@link MsnMessengerListener#logout(MsnMessenger)} has been received for * {@link #messenger} and it is thus an error to call * {@link MsnMessenger#logout()} on it. */ private boolean logoutReceived = false; /** * Returns the state of the registration of this protocol provider * @return the RegistrationState that this provider is * currently in or null in case it is in a unknown state. */ public RegistrationState getRegistrationState() { if(messenger == null || messenger.getConnection() == null) return RegistrationState.UNREGISTERED; else return RegistrationState.REGISTERED; } /** * Starts the registration process. Connection details such as * registration server, user name/number are provided through the * configuration service through implementation specific properties. * * @param authority the security authority that will be used for resolving * any security challenges that may be returned during the * registration or at any moment while wer're registered. * @throws OperationFailedException with the corresponding code it the * registration fails for some reason (e.g. a networking error or an * implementation problem). */ public void register(final SecurityAuthority authority) throws OperationFailedException { if(authority == null) throw new IllegalArgumentException( "The register method needs a valid non-null authority impl " + " in order to be able and retrieve passwords."); this.authority = authority; connectAndLogin(authority, SecurityAuthority.AUTHENTICATION_REQUIRED); } /** * Reconnects if fails fire connection failed. * @param reasonCode the appropriate SecurityAuthority reasonCode, * which would specify the reason for which we're re-calling the login. */ void reconnect(int reasonCode) { try { connectAndLogin(authority, reasonCode); } catch (OperationFailedException ex) { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); } } /** * Connects and logins to the server * @param authority SecurityAuthority * @param reasonCode * @throws OperationFailedException if login parameters * as server port are not correct */ private void connectAndLogin(SecurityAuthority authority, int reasonCode) throws OperationFailedException { synchronized(initializationLock) { //verify whether a password has already been stored for this account ProtocolProviderFactory protocolProviderFactory = MsnActivator.getProtocolProviderFactory(); AccountID accountID = getAccountID(); String password = protocolProviderFactory.loadPassword(accountID); //decode if (password == null) { //create a default credentials object UserCredentials credentials = new UserCredentials(); credentials.setUserName(accountID.getUserID()); //request a password from the user credentials = authority .obtainCredentials( accountID.getDisplayName(), credentials, reasonCode); // in case user has canceled the login window if(credentials == null) { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_USER_REQUEST, ""); return; } //extract the password the user passed us. char[] pass = credentials.getPassword(); // the user didn't provide us a password (canceled the operation) if(pass == null) { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_USER_REQUEST, ""); return; } password = new String(pass); if (credentials.isPasswordPersistent()) protocolProviderFactory.storePassword(accountID, password); } messenger = MsnMessengerFactory .createMsnMessenger(accountID.getUserID(), password); /* * We've just created the messenger so we're sure we haven't * received a logout for it. */ logoutReceived = false; messenger.addMessengerListener(new MsnConnectionListener()); persistentPresence.setMessenger(messenger); typingNotifications.setMessenger(messenger); fireRegistrationStateChanged( getRegistrationState(), RegistrationState.REGISTERING, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); try { messenger.login(); } catch (UnresolvedAddressException ex) { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, null); } catch(DnssecRuntimeException ex) { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_USER_REQUEST, null); } } } /** * Ends the registration of this protocol provider with the service. */ public void unregister() { unregister(true); } /* * (non-Javadoc) * * @see net.java.sip.communicator.service.protocol.ProtocolProviderService# * isSignallingTransportSecure() */ public boolean isSignalingTransportSecure() { return false; } /** * Returns the "transport" protocol of this instance used to carry the * control channel for the current protocol service. * * @return The "transport" protocol of this instance: TCP. */ public TransportProtocol getTransportProtocol() { return TransportProtocol.TCP; } /** * Unregister and fire the event if requested * @param fireEvent boolean */ void unregister(boolean fireEvent) { RegistrationState currRegState = getRegistrationState(); if(fireEvent) fireRegistrationStateChanged( currRegState, RegistrationState.UNREGISTERING, RegistrationStateChangeEvent.REASON_USER_REQUEST, null); // The synchronization is for logoutReceived at least. synchronized (initializationLock) { if((messenger != null) && !logoutReceived) messenger.logout(); persistentPresence.setMessenger(null); typingNotifications.setMessenger(null); } // if messenger is null we have already fired unregister if(fireEvent && messenger != null) fireRegistrationStateChanged( currRegState, RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_USER_REQUEST, null); } /** * Returns the short name of the protocol that the implementation of this * provider is based upon (like SIP, Msn, ICQ/AIM, or others for * example). * * @return a String containing the short name of the protocol this * service is taking care of. */ public String getProtocolName() { return ProtocolNames.MSN; } /** * Initialized the service implementation, and puts it in a sate where it * could interoperate with other services. It is strongly recomended that * properties in this Map be mapped to property names as specified by * AccountProperties. * * @param screenname the account id/uin/screenname of the account that * we're about to create * @param accountID the identifier of the account that this protocol * provider represents. * * @see net.java.sip.communicator.service.protocol.AccountID */ protected void initialize(String screenname, AccountID accountID) { synchronized(initializationLock) { this.accountID = accountID; addSupportedOperationSet( OperationSetInstantMessageTransform.class, new OperationSetInstantMessageTransformImpl()); //initialize the presence operationset persistentPresence = new OperationSetPersistentPresenceMsnImpl(this); addSupportedOperationSet( OperationSetPersistentPresence.class, persistentPresence); //register it once again for those that simply need presence addSupportedOperationSet( OperationSetPresence.class, persistentPresence); //initialize AccountInfo OperationSetServerStoredAccountInfoMsnImpl accountInfo = new OperationSetServerStoredAccountInfoMsnImpl( this, screenname); addSupportedOperationSet( OperationSetServerStoredAccountInfo.class, accountInfo); addSupportedOperationSet( OperationSetAvatar.class, new OperationSetAvatarMsnImpl(this, accountInfo)); addSupportedOperationSet( OperationSetAdHocMultiUserChat.class, new OperationSetAdHocMultiUserChatMsnImpl(this)); // initialize the IM operation set addSupportedOperationSet( OperationSetBasicInstantMessaging.class, new OperationSetBasicInstantMessagingMsnImpl(this)); //initialize the typing notifications operation set typingNotifications = new OperationSetTypingNotificationsMsnImpl(this); addSupportedOperationSet( OperationSetTypingNotifications.class, typingNotifications); addSupportedOperationSet( OperationSetFileTransfer.class, new OperationSetFileTransferMsnImpl(this)); } } /** * Makes the service implementation close all open sockets and release * any resources that it might have taken and prepare for * shutdown/garbage collection. */ public void shutdown() { synchronized(initializationLock) { unregister(false); messenger = null; } } /** * Returns the AccountID that uniquely identifies the account represented * by this instance of the ProtocolProviderService. * @return the id of the account represented by this provider. */ public AccountID getAccountID() { return accountID; } /** * Returns the XMPPConnectionopened by this provider * @return a reference to the XMPPConnection last opened by this * provider. */ MsnMessenger getMessenger() { return messenger; } /** * Creates a RegistrationStateChange event corresponding to the specified * old and new states and notifies all currently registered listeners. * * @param oldState the state that the provider had before the change * occurred * @param newState the state that the provider is currently in. * @param reasonCode a value corresponding to one of the REASON_XXX fields * of the RegistrationStateChangeEvent class, indicating the reason for * this state transition. * @param reason a String further explaining the reason code or null if * no such explanation is necessary. */ @Override public void fireRegistrationStateChanged(RegistrationState oldState, RegistrationState newState, int reasonCode, String reason) { if (newState.equals(RegistrationState.UNREGISTERED) || newState.equals(RegistrationState.CONNECTION_FAILED)) messenger = null; super.fireRegistrationStateChanged(oldState, newState, reasonCode, reason); } /** * Listens when we are logged in or out from the server or incoming * exception in the lib impl. */ private class MsnConnectionListener implements MsnMessengerListener { /** * Fired when login has completed. * @param msnMessenger */ public void loginCompleted(MsnMessenger msnMessenger) { if (logger.isTraceEnabled()) logger.trace("loginCompleted " + msnMessenger.getActualMsnProtocol()); fireRegistrationStateChanged( getRegistrationState(), RegistrationState.REGISTERED, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); } /** * Fire when lib logs out. * @param msnMessenger */ public void logout(MsnMessenger msnMessenger) { if (logger.isTraceEnabled()) logger.trace("logout"); // The synchronization is for logoutReceived at least. synchronized (initializationLock) { logoutReceived = true; unregister(true); } } /** * Fired when an exception has occurred. * @param msnMessenger * @param throwable */ public void exceptionCaught(MsnMessenger msnMessenger, Throwable throwable) { if(throwable instanceof IncorrectPasswordException) { unregister(false); MsnActivator.getProtocolProviderFactory(). storePassword(getAccountID(), null); fireRegistrationStateChanged( getRegistrationState(), RegistrationState.AUTHENTICATION_FAILED, RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED, "Incorrect Password"); // We try to reconnect and ask user to retype password. reconnect(SecurityAuthority.WRONG_PASSWORD); } else if(throwable instanceof SocketException) { // in case of SocketException just fire event and not trigger // unregister it will cause SocketException again and will loop fireRegistrationStateChanged( getRegistrationState(), RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, null); } else if(throwable instanceof UnknownHostException) { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, "A network error occured. Could not connect to server."); } else if(throwable instanceof MsnProtocolException) { MsnProtocolException exception = (MsnProtocolException)throwable; logger.error("Error in Msn lib ", exception); switch(exception.getErrorCode()) { case 500: case 540: case 601: if(isRegistered()) { unregister(false); fireRegistrationStateChanged( getRegistrationState(), RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, null); } break; case 911: if(isRegistered()) { unregister(false); MsnActivator.getProtocolProviderFactory(). storePassword(getAccountID(), null); fireRegistrationStateChanged( getRegistrationState(), RegistrationState.AUTHENTICATION_FAILED, RegistrationStateChangeEvent .REASON_AUTHENTICATION_FAILED, null); // We try to reconnect and ask user to retype // password. reconnect(SecurityAuthority.WRONG_PASSWORD); } break; } } else { logger.error("Error in Msn lib ", throwable); if(throwable instanceof LoginException) { MsnActivator.getProtocolProviderFactory(). storePassword(getAccountID(), null); fireRegistrationStateChanged( getRegistrationState(), RegistrationState.AUTHENTICATION_FAILED, RegistrationStateChangeEvent .REASON_AUTHENTICATION_FAILED, null); // We try to reconnect and ask user to retype // password. reconnect(SecurityAuthority.WRONG_PASSWORD); } // We don't want to disconnect on any error, that's why we're // commenting the following lines for now. // // if(isRegistered()) // { // unregister(false); // fireRegistrationStateChanged( // getRegistrationState(), // RegistrationState.UNREGISTERED, // RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); // } } } } /** * Returns the msn protocol icon. * @return the msn protocol icon */ public ProtocolIcon getProtocolIcon() { return msnIcon; } }