/*
* 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;
}
}