/* * 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.impl.protocol.icq; import java.util.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import net.kano.joscar.flap.*; import net.kano.joscar.flapcmd.*; import net.kano.joscar.net.*; import net.kano.joscar.snaccmd.auth.*; import net.kano.joustsim.*; import net.kano.joustsim.oscar.*; import net.kano.joustsim.oscar.oscar.loginstatus.*; import net.kano.joustsim.oscar.oscar.service.*; import net.kano.joustsim.oscar.oscar.service.icbm.*; import net.kano.joustsim.oscar.proxy.*; /** * An implementation of the protocol provider service over the AIM/ICQ protocol * * @author Emil Ivov * @author Damian Minkov * @author Valentin Martinet */ public class ProtocolProviderServiceIcqImpl extends AbstractProtocolProviderService { /** * This class logger. */ private static final Logger logger = Logger.getLogger(ProtocolProviderServiceIcqImpl.class); /** * Application session. */ private DefaultAppSession session = null; /** * Protocol stack session. */ private AimSession aimSession = null; /** * Protocol stack connection. */ private AimConnection aimConnection = null; /** * Messenger service. */ private IcbmService icbmService = null; /** * Listener that catches all connection events originating from joscar * during connection to icq. */ private AimConnStateListener aimConnStateListener = null; /** * Listener that catches all incoming and outgoing chat events generated * by joscar. */ private AimIcbmListener aimIcbmListener = new AimIcbmListener(); /** * indicates whether or not the provider is initialized and ready for use. */ private boolean isInitialized = false; /** * We use this to lock access to initialization. */ private Object initializationLock = new Object(); /** * The identifier of the account that this provider represents. */ private AccountID accountID = null; /** * Retrieves short or full user info, such as Name, Address, Nickname etc. */ private InfoRetreiver infoRetreiver = null; /** * The icon corresponding to the icq protocol. */ private ProtocolIconIcqImpl icqIcon = new ProtocolIconIcqImpl(); /** * The icon corresponding to the aim protocol. */ private ProtocolIconAimImpl aimIcon = new ProtocolIconAimImpl(); /** * Property whether we are using AIM or ICQ service */ boolean USING_ICQ = true; /** * Used when we need to re-register */ private SecurityAuthority authority = null; /** * Keeping track of the last fired registration state. */ private RegistrationState lastRegistrationState = null; /** * The server to use for aim service. */ private static final String AIM_DEFAULT_LOGIN_SERVER = "login.oscar.aol.com"; /** * The server to use for icq service. */ private static final String ICQ_DEFAULT_LOGIN_SERVER = "login.icq.com"; /** * Returns the state of the registration of this protocol provider * @return the <tt>RegistrationState</tt> that this provider is * currently in or null in case it is in a unknown state. */ public RegistrationState getRegistrationState() { if(getAimConnection() == null || lastRegistrationState == null) return RegistrationState.UNREGISTERED; return lastRegistrationState; } /** * Converts the specified joust sim connection state to a corresponding * RegistrationState. * @param joustSimConnState the joust sim connection state. * @param joustSimConnStateInfo additional stateinfo if available (may be * null) * @return a RegistrationState corresponding best to the specified * joustSimState. */ private RegistrationState joustSimStateToRegistrationState( State joustSimConnState, StateInfo joustSimConnStateInfo) { if(joustSimConnState == State.ONLINE) return RegistrationState.REGISTERED; else if (joustSimConnState == State.CONNECTING) return RegistrationState.REGISTERING; else if( joustSimConnState == State.AUTHORIZING) return RegistrationState.AUTHENTICATING; else if (joustSimConnState == State.CONNECTINGAUTH) return RegistrationState.AUTHENTICATING; else if (joustSimConnState == State.SIGNINGON) return RegistrationState.REGISTERING; else if (joustSimConnState == State.DISCONNECTED || joustSimConnState == State.NOTCONNECTED) { if(joustSimConnStateInfo != null && joustSimConnStateInfo instanceof DisconnectedStateInfo && !((DisconnectedStateInfo)joustSimConnStateInfo).isOnPurpose()) { return RegistrationState.CONNECTION_FAILED; } return RegistrationState.UNREGISTERED; } else if (joustSimConnState == State.FAILED) { if(joustSimConnStateInfo != null && joustSimConnStateInfo instanceof LoginFailureStateInfo) { LoginFailureInfo lfInfo = ((LoginFailureStateInfo) joustSimConnStateInfo).getLoginFailureInfo(); if (lfInfo instanceof AuthFailureInfo) return RegistrationState.AUTHENTICATION_FAILED; } return RegistrationState.CONNECTION_FAILED; } else{ logger.warn("Unknown state " + joustSimConnState + ". Defaulting to " + RegistrationState.UNREGISTERED); return RegistrationState.UNREGISTERED; } } /** * 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(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."); // Keep the authority in case we need to re-register. this.authority = authority; connectAndLogin(authority, SecurityAuthority.AUTHENTICATION_REQUIRED); } /** * Reconnects if fails fire connection failed. * @param reasonCode the appropriate <tt>SecurityAuthority</tt> 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 reason code in case of reconnect. * @throws OperationFailedException if login parameters * as server port are not correct */ private void connectAndLogin(SecurityAuthority authority, int reasonCode) throws OperationFailedException { synchronized(initializationLock) { ProtocolProviderFactoryIcqImpl protocolProviderFactory = null; if(USING_ICQ) protocolProviderFactory = IcqActivator.getIcqProtocolProviderFactory(); else protocolProviderFactory = IcqActivator.getAimProtocolProviderFactory(); //verify whether a password has already been stored for this account String password = protocolProviderFactory.loadPassword(getAccountID()); //decode if( password == null ) { //create a default credentials object UserCredentials credentials = new UserCredentials(); credentials.setUserName(this.getAccountID().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( RegistrationState.UNREGISTERED, RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_USER_REQUEST, ""); return; } password = new String(pass); if (credentials.isPasswordPersistent()) { protocolProviderFactory .storePassword(getAccountID(), password); } } //init the necessary objects session = new DefaultAppSession(); aimSession = session.openAimSession( new Screenname(getAccountID().getUserID())); String globalProxyType = IcqActivator.getConfigurationService() .getString(ProxyInfo.CONNECTION_PROXY_TYPE_PROPERTY_NAME); if(globalProxyType != null && globalProxyType.equals(ProxyInfo.ProxyType.HTTP.name())) { String globalProxyAddress = IcqActivator.getConfigurationService().getString( ProxyInfo.CONNECTION_PROXY_ADDRESS_PROPERTY_NAME); String globalProxyPortStr = IcqActivator.getConfigurationService().getString( ProxyInfo.CONNECTION_PROXY_PORT_PROPERTY_NAME); int proxyPort; try { proxyPort = Integer.parseInt(globalProxyPortStr); } catch (NumberFormatException ex) { throw new OperationFailedException("Wrong port", OperationFailedException.INVALID_ACCOUNT_PROPERTIES, ex); } String globalProxyUsername = IcqActivator.getConfigurationService().getString( ProxyInfo.CONNECTION_PROXY_USERNAME_PROPERTY_NAME); String globalProxyPassword = IcqActivator.getConfigurationService().getString( ProxyInfo.CONNECTION_PROXY_PASSWORD_PROPERTY_NAME); if(globalProxyAddress == null || globalProxyAddress.length() <= 0) { throw new OperationFailedException( "Missing Proxy Address", OperationFailedException.INVALID_ACCOUNT_PROPERTIES); } // If we are using http proxy, sometimes // default port 5190 is forbidden, so force // http/https port AimConnectionProperties connProps = new AimConnectionProperties( new Screenname(getAccountID().getUserID()) , password); if(USING_ICQ) connProps.setLoginHost(ICQ_DEFAULT_LOGIN_SERVER); else connProps.setLoginHost(AIM_DEFAULT_LOGIN_SERVER); connProps.setLoginPort(443); aimConnection = aimSession.openConnection(connProps); aimConnection.setProxy(AimProxyInfo.forHttp( globalProxyAddress, proxyPort, globalProxyUsername, globalProxyPassword)); } else { AimConnectionProperties connProps = new AimConnectionProperties( new Screenname(getAccountID().getUserID()) , password); if(USING_ICQ) connProps.setLoginHost(ICQ_DEFAULT_LOGIN_SERVER); else connProps.setLoginHost(AIM_DEFAULT_LOGIN_SERVER); aimConnection = aimSession.openConnection(connProps); } aimConnStateListener = new AimConnStateListener(); aimConnection.addStateListener(aimConnStateListener); aimConnection.connect(); } } /** * Ends the registration of this protocol provider with the service. */ public void unregister() { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.UNREGISTERING, RegistrationStateChangeEvent.REASON_USER_REQUEST, null); if(aimConnection != null) aimConnection.disconnect(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; } /** * Returns the short name of the protocol that the implementation of this * provider is based upon (like SIP, Jabber, 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() { if(USING_ICQ) return ProtocolNames.ICQ; else return ProtocolNames.AIM; } /** * Returns the protocol display name. This is the name that would be used * by the GUI to display the protocol name. * * @return a String containing the display name of the protocol this service * is implementing */ @Override public String getProtocolDisplayName() { if(USING_ICQ) return ProtocolNames.ICQ; else return ProtocolNames.AIM; } /** * 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 * <tt>AccountProperties</tt>. * * @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; if(IcqAccountID.isAIM(accountID.getAccountProperties())) USING_ICQ = false; addSupportedOperationSet( OperationSetInstantMessageTransform.class, new OperationSetInstantMessageTransformImpl()); //initialize the presence operationset OperationSetPersistentPresence persistentPresence = new OperationSetPersistentPresenceIcqImpl(this, screenname); addSupportedOperationSet( OperationSetPersistentPresence.class, persistentPresence); //register it once again for those that simply need presence addSupportedOperationSet( OperationSetPresence.class, persistentPresence); //initialize the IM operation set addSupportedOperationSet( OperationSetBasicInstantMessaging.class, new OperationSetBasicInstantMessagingIcqImpl(this)); //initialize the multi chat operation set addSupportedOperationSet( OperationSetAdHocMultiUserChat.class, new OperationSetAdHocMultiUserChatIcqImpl(this)); //initialize the typing notifications operation set addSupportedOperationSet( OperationSetTypingNotifications.class, new OperationSetTypingNotificationsIcqImpl(this)); if(USING_ICQ) { this.infoRetreiver = new InfoRetreiver(this, screenname); addSupportedOperationSet( OperationSetServerStoredContactInfo.class, new OperationSetServerStoredContactInfoIcqImpl( infoRetreiver, this)); OperationSetServerStoredAccountInfoIcqImpl serverStoredAccountInfoOpSet = new OperationSetServerStoredAccountInfoIcqImpl( infoRetreiver, screenname, this); addSupportedOperationSet( OperationSetServerStoredAccountInfo.class, serverStoredAccountInfoOpSet); // Currently disabled as when we send avatar // we receive an error from server // addSupportedOperationSet( // OperationSetAvatar.class, // new OperationSetAvatarIcqImpl( // this, // serverStoredAccountInfoOpSet)); addSupportedOperationSet( OperationSetWebAccountRegistration.class, new OperationSetWebAccountRegistrationIcqImpl()); addSupportedOperationSet( OperationSetWebContactInfo.class, new OperationSetWebContactInfoIcqImpl()); addSupportedOperationSet( OperationSetExtendedAuthorizations.class, new OperationSetExtendedAuthorizationsIcqImpl(this)); } addSupportedOperationSet( OperationSetFileTransfer.class, new OperationSetFileTransferIcqImpl(this)); isInitialized = true; } } /** * 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() { /** @todo is there anything else to add here? */ synchronized(initializationLock){ icbmService = null; session = null; aimSession = null; aimConnection = null; aimConnStateListener = null; aimIcbmListener = null; isInitialized = false; } } /** * Returns true if the provider service implementation is initialized and * ready for use by other services, and false otherwise. * * @return true if the provider is initialized and ready for use and false * otherwise */ public boolean isInitialized() { return isInitialized; } /** * 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; } /** * Creates a RegistrationStateChange event corresponding to the specified * old and new joust sim states and notifies all currently registered * listeners. * * @param oldJoustSimState the state that the joust sim connection had * before the change occurred * @param oldJoustSimStateInfo the state info associated with the state of * the underlying connection state as it is after the change. * @param newJoustSimState the state that the underlying joust sim * connection is currently in. * @param newJoustSimStateInfo the state info associated with the state of * the underlying connection state as it was before the change. * @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. */ private void fireRegistrationStateChanged( State oldJoustSimState, StateInfo oldJoustSimStateInfo, State newJoustSimState, StateInfo newJoustSimStateInfo, int reasonCode, String reason) { RegistrationState oldRegistrationState = joustSimStateToRegistrationState(oldJoustSimState , oldJoustSimStateInfo); RegistrationState newRegistrationState = joustSimStateToRegistrationState(newJoustSimState , newJoustSimStateInfo); fireRegistrationStateChanged(oldRegistrationState, newRegistrationState , reasonCode, reason); } /** * 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) { lastRegistrationState = newState; super.fireRegistrationStateChanged( oldState, newState, reasonCode, reason); } /** * Returns the info retriever that we've initialized for the current * session. * * @return the info retriever that we've initialized for the current * session. */ protected InfoRetreiver getInfoRetreiver() { return infoRetreiver; } /** * This class handles connection state events that have originated in this * provider's aim connection. Events are acted upon accordingly and, * if necessary, forwarded to registered listeners (asynchronously). */ private class AimConnStateListener implements StateListener { /** * Connection state changes being reported here. * @param event */ public void handleStateChange(StateEvent event) { State newState = event.getNewState(); State oldState = event.getOldState(); AimConnection conn = event.getAimConnection(); if (logger.isDebugEnabled()) logger.debug("ICQ protocol provider " + getProtocolName() + " changed registration status from " + oldState + " to " + newState); int reasonCode = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED; String reasonStr = null; if (newState == State.ONLINE) { icbmService = conn.getIcbmService(); icbmService.addIcbmListener(aimIcbmListener); conn.getInfoService(). getOscarConnection().getSnacProcessor(). getFlapProcessor().addPacketListener( new ConnectionClosedListener(conn)); } else if (newState == State.DISCONNECTED) { // we need a Service here. no metter which // I've choose BosService // we just need the oscar conenction from the service Service service = aimConnection.getBosService(); if(service != null) { int discconectCode = service.getOscarConnection() .getLastCloseCode(); reasonCode = ConnectionClosedListener .convertCodeToRegistrationStateChangeEvent( discconectCode); reasonStr = ConnectionClosedListener .convertCodeToStringReason(discconectCode); if (logger.isDebugEnabled()) logger.debug( "The aim Connection was disconnected! with reason : " + reasonStr); } if(reasonCode == RegistrationStateChangeEvent.REASON_NOT_SPECIFIED && event.getNewStateInfo() instanceof DisconnectedStateInfo) { // if its on purpose it is user request if(((DisconnectedStateInfo)event.getNewStateInfo()).isOnPurpose()) { reasonCode = RegistrationStateChangeEvent.REASON_USER_REQUEST; } } else if (logger.isDebugEnabled()) logger.debug("The aim Connection was disconnected!"); } else if(newState == State.FAILED) { // assume that a failure during connect&resolve is a DNSSEC // validation error if (oldState == State.CONNECTINGAUTH && conn.getLoginService() != null && conn.getLoginService().getOscarConnection() != null && conn.getLoginService().getOscarConnection() .getConnectionState() == ClientConn.STATE_RESOLVING ) { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_USER_REQUEST, "Disconnected due to assumed DNSSEC failure"); return; } else if (logger.isDebugEnabled()) logger.debug("The aim Connection failed! " + event.getNewStateInfo()); } if(event.getNewStateInfo() instanceof LoginFailureStateInfo) { LoginFailureInfo loginFailure = ((LoginFailureStateInfo)event.getNewStateInfo()) .getLoginFailureInfo(); if(loginFailure instanceof AuthFailureInfo) { AuthFailureInfo afi = (AuthFailureInfo)loginFailure; if (logger.isDebugEnabled()) logger.debug("AuthFailureInfo code : " + afi.getErrorCode()); int code = ConnectionClosedListener .convertAuthCodeToReasonCode(afi); reasonCode = ConnectionClosedListener .convertCodeToRegistrationStateChangeEvent(code); reasonStr = ConnectionClosedListener .convertCodeToStringReason(code); } } //as a side note - if this was an AuthenticationFailed error //set the stored password to null so that we don't use it any more. if(reasonCode == RegistrationStateChangeEvent .REASON_AUTHENTICATION_FAILED) { if(USING_ICQ) IcqActivator.getIcqProtocolProviderFactory().storePassword( getAccountID(), null); else IcqActivator.getAimProtocolProviderFactory().storePassword( getAccountID(), null); reconnect(SecurityAuthority.WRONG_PASSWORD); } if (newState == State.ONLINE) { // we will fire FINALIZING_REGISTRATION and will wait // for the registration to finnish. fireRegistrationStateChanged(lastRegistrationState, RegistrationState.FINALIZING_REGISTRATION, -1, null); // we must wait a little bit before firing registered // event , waiting for ClientReadyCommand to be sent successfully new RegisteredEventThread().start(); } else { //now tell all interested parties about what happened. fireRegistrationStateChanged( oldState, event.getOldStateInfo(), newState, event.getNewStateInfo(), reasonCode, reasonStr); } } } /** * Throw registered with 2 seconds delay. * We must wait a little bit before firing registered * event , waiting for ClientReadyCommand to be sent successfully */ private class RegisteredEventThread extends Thread { @Override public void run() { Object w = new Object(); synchronized(w) { try { w.wait(2000); } catch (Exception e) {} } fireRegistrationStateChanged(lastRegistrationState, RegistrationState.REGISTERED, -1, null); } } /** * Returns the <tt>AimSession</tt> opened by this provider. * @return a reference to the <tt>AimSession</tt> that this provider * last opened. */ protected AimSession getAimSession() { return aimSession; } /** * Returns the <tt>AimConnection</tt>opened by this provider * @return a reference to the <tt>AimConnection</tt> last opened by this * provider. */ protected AimConnection getAimConnection() { return aimConnection; } /** * Message listener. */ public static class AimIcbmListener implements IcbmListener { /** * New conversations. * @param service the messenger service. * @param conv the new conversation. */ public void newConversation(IcbmService service, Conversation conv) { if (logger.isDebugEnabled()) logger.debug("Received a new conversation event"); conv.addConversationListener(new AimConversationListener()); } /** * Does nothing. * @param service * @param buddy * @param info */ public void buddyInfoUpdated(IcbmService service, Screenname buddy, IcbmBuddyInfo info) { if (logger.isDebugEnabled()) logger.debug("Got a BuddINFO event"); } /** * Does nothing. * @param service * @param message * @param triedConversations */ public void sendAutomaticallyFailed( IcbmService service, net.kano.joustsim.oscar.oscar.service.icbm.Message message, Set<Conversation> triedConversations) { } } public static class AimConversationListener implements ConversationListener { public void sentOtherEvent(Conversation conversation, ConversationEventInfo event) { if (logger.isDebugEnabled()) logger.debug("reveived ConversationEventInfo:" + event); } // This may be called without ever calling conversationOpened public void conversationClosed(Conversation co) { if (logger.isDebugEnabled()) logger.debug("conversation closed"); } public void gotOtherEvent(Conversation conversation, ConversationEventInfo event) { if (logger.isDebugEnabled()) logger.debug("goet other event"); if(event instanceof TypingInfo) { TypingInfo ti = (TypingInfo)event; if (logger.isDebugEnabled()) logger.debug("got typing info and state is: " + ti.getTypingState()); } else if (event instanceof MessageInfo) { MessageInfo ti = (MessageInfo)event; if (logger.isDebugEnabled()) logger.debug("got message info for msg: " + ti.getMessage()); } } public void canSendMessageChanged(Conversation con, boolean canSend) { if (logger.isDebugEnabled()) logger.debug("can send message event"); } // This may never be called public void conversationOpened(Conversation con) { if (logger.isDebugEnabled()) logger.debug("conversation opened event"); } // This may be called after conversationClosed is called public void sentMessage(Conversation con, MessageInfo minfo) { if (logger.isDebugEnabled()) logger.debug("sent message event"); } // This may be called after conversationClosed is called. public void gotMessage(Conversation con, MessageInfo minfo) { if (logger.isDebugEnabled()) logger.debug("got message event" + minfo.getMessage().getMessageBody()); } } /** * Fix for late close connection due to * multiple logins. * Listening for incoming packets for the close command * when this is received we disconnect the session to force it * because otherwise is wait for timeout of reading from the socket stream * which leads to from 10 to 20 seconds delay of closing the session * and connection * */ public static class ConnectionClosedListener implements FlapPacketListener { private AimConnection aimConnection = null; private final static int REASON_MULTIPLE_LOGINS = 0x0001; private final static int REASON_BAD_PASSWORD_A = 0x0004; private final static int REASON_BAD_PASSWORD_B = 0x0005; private final static int REASON_NON_EXISTING_ICQ_UIN_A = 0x0007; private final static int REASON_NON_EXISTING_ICQ_UIN_B = 0x0008; private final static int REASON_MANY_CLIENTS_FROM_SAME_IP_A = 0x0015; private final static int REASON_MANY_CLIENTS_FROM_SAME_IP_B = 0x0016; private final static int REASON_CONNECTION_RATE_EXCEEDED = 0x0018; private final static int REASON_CONNECTION_TOO_FAST = 0x001D; private final static int REASON_TRY_AGAIN = 0x001E; private final static String REASON_STRING_MULTIPLE_LOGINS = "multiple logins (on same UIN)"; private final static String REASON_STRING_BAD_PASSWORD = "bad password"; private final static String REASON_STRING_NON_EXISTING_ICQ_UIN = "non-existant UIN"; private final static String REASON_STRING_MANY_CLIENTS_FROM_SAME_IP = "too many clients from same IP"; private final static String REASON_STRING_CONNECTION_RATE_EXCEEDED = "Rate exceeded. The server temporarily bans you."; private final static String REASON_STRING_CONNECTION_TOO_FAST = "You are reconnecting too fast"; private final static String REASON_STRING_TRY_AGAIN = "Can't register on ICQ network, try again soon."; private final static String REASON_STRING_NOT_SPECIFIED = "Not Specified"; ConnectionClosedListener(AimConnection aimConnection) { this.aimConnection = aimConnection; } public void handleFlapPacket(FlapPacketEvent evt) { FlapCommand flapCommand = evt.getFlapCommand(); if (flapCommand instanceof CloseFlapCmd) { CloseFlapCmd closeCmd = (CloseFlapCmd)flapCommand; if (logger.isTraceEnabled()) logger.trace("received close command with code : " + closeCmd.getCode()); aimConnection.disconnect(); } } /** * Converts the codes in the close command * or the lastCloseCode of OscarConnection to the states * which are registered in the service protocol events * * @param reasonCode int the reason of close connection * @return int corresponding RegistrationStateChangeEvent */ static int convertCodeToRegistrationStateChangeEvent(int reasonCode) { switch(reasonCode) { case REASON_MULTIPLE_LOGINS : return RegistrationStateChangeEvent .REASON_MULTIPLE_LOGINS; case REASON_BAD_PASSWORD_A : return RegistrationStateChangeEvent .REASON_AUTHENTICATION_FAILED; case REASON_BAD_PASSWORD_B : return RegistrationStateChangeEvent .REASON_AUTHENTICATION_FAILED; case REASON_NON_EXISTING_ICQ_UIN_A : return RegistrationStateChangeEvent .REASON_NON_EXISTING_USER_ID; case REASON_NON_EXISTING_ICQ_UIN_B : return RegistrationStateChangeEvent .REASON_NON_EXISTING_USER_ID; case REASON_MANY_CLIENTS_FROM_SAME_IP_A : return RegistrationStateChangeEvent .REASON_CLIENT_LIMIT_REACHED_FOR_IP; case REASON_MANY_CLIENTS_FROM_SAME_IP_B : return RegistrationStateChangeEvent .REASON_CLIENT_LIMIT_REACHED_FOR_IP; case REASON_CONNECTION_RATE_EXCEEDED : return RegistrationStateChangeEvent .REASON_RECONNECTION_RATE_LIMIT_EXCEEDED; case REASON_CONNECTION_TOO_FAST : return RegistrationStateChangeEvent .REASON_RECONNECTION_RATE_LIMIT_EXCEEDED; case REASON_TRY_AGAIN : return RegistrationStateChangeEvent .REASON_RECONNECTION_RATE_LIMIT_EXCEEDED; default : return RegistrationStateChangeEvent .REASON_NOT_SPECIFIED; } } /** * returns the reason string corresponding to the code * in the close command * * @param reasonCode int the reason of close connection * @return String describing the reason */ static String convertCodeToStringReason(int reasonCode) { switch(reasonCode) { case REASON_MULTIPLE_LOGINS : return REASON_STRING_MULTIPLE_LOGINS; case REASON_BAD_PASSWORD_A : return REASON_STRING_BAD_PASSWORD; case REASON_BAD_PASSWORD_B : return REASON_STRING_BAD_PASSWORD; case REASON_NON_EXISTING_ICQ_UIN_A : return REASON_STRING_NON_EXISTING_ICQ_UIN; case REASON_NON_EXISTING_ICQ_UIN_B : return REASON_STRING_NON_EXISTING_ICQ_UIN; case REASON_MANY_CLIENTS_FROM_SAME_IP_A : return REASON_STRING_MANY_CLIENTS_FROM_SAME_IP; case REASON_MANY_CLIENTS_FROM_SAME_IP_B : return REASON_STRING_MANY_CLIENTS_FROM_SAME_IP; case REASON_CONNECTION_RATE_EXCEEDED : return REASON_STRING_CONNECTION_RATE_EXCEEDED; case REASON_CONNECTION_TOO_FAST : return REASON_STRING_CONNECTION_TOO_FAST; case REASON_TRY_AGAIN : return REASON_STRING_TRY_AGAIN; default : return REASON_STRING_NOT_SPECIFIED; } } /** * When receiving login failure * the reasons for the failure are in the authorization * part of the protocol ( 0x13 ) * In the AuthResponse are the possible reason codes * here they are converted to those in the ConnectionClosedListener * so the they can be converted to the one in service protocol events * * @param afi AuthFailureInfo the failure info * @return int the corresponding code to this failure */ static int convertAuthCodeToReasonCode(AuthFailureInfo afi) { switch(afi.getErrorCode()) { case AuthResponse.ERROR_BAD_PASSWORD : return REASON_BAD_PASSWORD_A; case AuthResponse.ERROR_CONNECTING_TOO_MUCH_A : return REASON_CONNECTION_RATE_EXCEEDED; case AuthResponse.ERROR_CONNECTING_TOO_MUCH_B : return REASON_CONNECTION_RATE_EXCEEDED; case AuthResponse.ERROR_INVALID_SN_OR_PASS_A : return REASON_NON_EXISTING_ICQ_UIN_A; case AuthResponse.ERROR_INVALID_SN_OR_PASS_B : return REASON_NON_EXISTING_ICQ_UIN_B; // 16 is also used for blocked from same IP case 16 : return REASON_MANY_CLIENTS_FROM_SAME_IP_A; case AuthResponse.ERROR_SIGNON_BLOCKED : return REASON_MANY_CLIENTS_FROM_SAME_IP_B; default : return RegistrationStateChangeEvent.REASON_NOT_SPECIFIED; } } } /** * Returns the icq/aim protocol icon. * @return the icq/aim protocol icon */ public ProtocolIcon getProtocolIcon() { if(USING_ICQ) return icqIcon; else return aimIcon; } }