/*
* 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.jabber;
import java.math.*;
import java.net.*;
import java.security.*;
import java.security.cert.*;
import java.text.*;
import java.util.*;
import javax.net.ssl.*;
import net.java.sip.communicator.impl.protocol.jabber.debugger.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.carbon.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.coin.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.inputevt.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jibri.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingle.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.jingleinfo.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.keepalive.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.messagecorrection.*;
import net.java.sip.communicator.impl.protocol.jabber.extensions.version.*;
import net.java.sip.communicator.service.certificate.*;
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.service.protocol.jabber.*;
import net.java.sip.communicator.service.protocol.jabberconstants.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.Logger;
import org.jitsi.service.configuration.*;
import org.jitsi.service.neomedia.*;
import org.jitsi.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.provider.*;
import org.jivesoftware.smack.util.*;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.*;
import org.jivesoftware.smackx.packet.*;
import org.xmlpull.v1.*;
import org.xmpp.jnodes.smack.*;
/**
* An implementation of the protocol provider service over the Jabber protocol
*
* @author Damian Minkov
* @author Symphorien Wanko
* @author Lyubomir Marinov
* @author Yana Stamcheva
* @author Emil Ivov
* @author Hristo Terezov
*/
public class ProtocolProviderServiceJabberImpl
extends AbstractProtocolProviderService
{
/**
* Logger of this class
*/
private static final Logger logger =
Logger.getLogger(ProtocolProviderServiceJabberImpl.class);
/**
* Jingle's Discovery Info common URN.
*/
public static final String URN_XMPP_JINGLE = JingleIQ.NAMESPACE;
/**
* Jingle's Discovery Info URN for RTP support.
*/
public static final String URN_XMPP_JINGLE_RTP
= RtpDescriptionPacketExtension.NAMESPACE;
/**
* Jingle's Discovery Info URN for RTP support with audio.
*/
public static final String URN_XMPP_JINGLE_RTP_AUDIO
= "urn:xmpp:jingle:apps:rtp:audio";
/**
* Jingle's Discovery Info URN for RTP support with video.
*/
public static final String URN_XMPP_JINGLE_RTP_VIDEO
= "urn:xmpp:jingle:apps:rtp:video";
/**
* Jingle's Discovery Info URN for ZRTP support with RTP.
*/
public static final String URN_XMPP_JINGLE_RTP_ZRTP
= ZrtpHashPacketExtension.NAMESPACE;
/**
* Jingle's Discovery Info URN for ICE_UDP transport support.
*/
public static final String URN_XMPP_JINGLE_RAW_UDP_0
= RawUdpTransportPacketExtension.NAMESPACE;
/**
* Jingle's Discovery Info URN for ICE_UDP transport support.
*/
public static final String URN_XMPP_JINGLE_ICE_UDP_1
= IceUdpTransportPacketExtension.NAMESPACE;
/**
* Jingle's Discovery Info URN for Jingle Nodes support.
*/
public static final String URN_XMPP_JINGLE_NODES
= "http://jabber.org/protocol/jinglenodes";
/**
* Jingle's Discovery Info URN for "XEP-0251: Jingle Session Transfer"
* support.
*/
public static final String URN_XMPP_JINGLE_TRANSFER_0
= TransferPacketExtension.NAMESPACE;
/**
* Jingle's Discovery Info URN for "XEP-298 :Delivering Conference
* Information to Jingle Participants (Coin)" support.
*/
public static final String URN_XMPP_JINGLE_COIN = "urn:xmpp:coin";
/**
* Jingle's Discovery Info URN for "XEP-0320: Use of DTLS-SRTP in
* Jingle Sessions".
*/
public static final String URN_XMPP_JINGLE_DTLS_SRTP
= "urn:xmpp:jingle:apps:dtls:0";
/**
* Discovery Info URN for classic RFC3264-style Offer/Answer negotiation
* with no support for Trickle ICE and low tolerance to transport/payload
* separation. Defined in XEP-0176
*/
public static final String URN_IETF_RFC_3264 = "urn:ietf:rfc:3264";
/**
* http://xmpp.org/extensions/xep-0092.html Software Version.
*
*/
// Used in JVB
@SuppressWarnings("unused")
public static final String URN_XMPP_IQ_VERSION = "jabber:iq:version";
/**
* Jingle's Discovery Info URN for "XEP-0294: Jingle RTP Header Extensions
* Negotiation" support.
*/
public static final String URN_XMPP_JINGLE_RTP_HDREXT =
"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
/**
* URN for XEP-0077 inband registration
*/
public static final String URN_REGISTER = "jabber:iq:register";
/**
* The name of the property under which the user may specify if the desktop
* streaming or sharing should be disabled.
*/
private static final String IS_DESKTOP_STREAMING_DISABLED
= "net.java.sip.communicator.impl.protocol.jabber." +
"DESKTOP_STREAMING_DISABLED";
/**
* The name of the property under which the user may specify if audio/video
* calls should be disabled.
*/
private static final String IS_CALLING_DISABLED
= "net.java.sip.communicator.impl.protocol.jabber.CALLING_DISABLED";
/**
* Smack packet reply timeout.
*/
public static final int SMACK_PACKET_REPLY_TIMEOUT = 45000;
/**
* Property for vcard reply timeout. Time to wait before
* we think vcard retrieving has timeouted, default value
* of smack is 5000 (5 sec.).
*/
public static final String VCARD_REPLY_TIMEOUT_PROPERTY =
"net.java.sip.communicator.impl.protocol.jabber.VCARD_REPLY_TIMEOUT";
/**
* XMPP signaling DSCP configuration property name.
*/
private static final String XMPP_DSCP_PROPERTY =
"net.java.sip.communicator.impl.protocol.XMPP_DSCP";
/**
* Indicates if user search is disabled.
*/
private static final String IS_USER_SEARCH_ENABLED_PROPERTY
= "USER_SEARCH_ENABLED";
/**
* Google voice domain name.
*/
public static final String GOOGLE_VOICE_DOMAIN = "voice.google.com";
/**
* Used to connect to a XMPP server.
*/
private Connection connection;
/**
* The socket address of the XMPP server.
*/
private InetSocketAddress address;
/**
* 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 final Object initializationLock = new Object();
/**
* The identifier of the account that this provider represents.
*/
private JabberAccountID accountID = null;
/**
* Used when we need to re-register
*/
private SecurityAuthority authority = null;
/**
* The resource we will use when connecting during this run.
*/
private String resource = null;
/**
* The icon corresponding to the jabber protocol.
*/
private ProtocolIconJabberImpl jabberIcon;
/**
* A set of features supported by our Jabber implementation.
* In general, we add new feature(s) when we add new operation sets.
* (see xep-0030 : http://www.xmpp.org/extensions/xep-0030.html#info).
* Example : to tell the world that we support jingle, we simply have
* to do :
* supportedFeatures.add("http://www.xmpp.org/extensions/xep-0166.html#ns");
* Beware there is no canonical mapping between op set and jabber features
* (op set is a SC "concept"). This means that one op set in SC can
* correspond to many jabber features. It is also possible that there is no
* jabber feature corresponding to a SC op set or again,
* we can currently support some features wich do not have a specific
* op set in SC (the mandatory feature :
* http://jabber.org/protocol/disco#info is one example).
* We can find features corresponding to op set in the xep(s) related
* to implemented functionality.
*/
private final List<String> supportedFeatures = new ArrayList<String>();
/**
* The <tt>ServiceDiscoveryManager</tt> is responsible for advertising
* <tt>supportedFeatures</tt> when asked by a remote client. It can also
* be used to query remote clients for supported features.
*/
private ScServiceDiscoveryManager discoveryManager = null;
/**
* The <tt>OperationSetContactCapabilities</tt> of this
* <tt>ProtocolProviderService</tt> which is the service-public counterpart
* of {@link #discoveryManager}.
*/
private OperationSetContactCapabilitiesJabberImpl opsetContactCapabilities;
/**
* The statuses.
*/
private JabberStatusEnum jabberStatusEnum;
/**
* The service we use to interact with user.
*/
private CertificateService guiVerification;
/**
* Used with tls connecting when certificates are not trusted
* and we ask the user to confirm connection. When some timeout expires
* connect method returns, and we use abortConnecting to abort further
* execution cause after user chooses we make further processing from there.
*/
private boolean abortConnecting = false;
/**
* Flag indicating are we currently executing connectAndLogin method.
*/
private boolean inConnectAndLogin = false;
/**
* Object used to synchronize the flag inConnectAndLogin.
*/
private final Object connectAndLoginLock = new Object();
/**
* If an event occurs during login we fire it at the end of the login
* process (at the end of connectAndLogin method).
*/
private RegistrationStateChangeEvent eventDuringLogin;
/**
* Listens for connection closes or errors.
*/
private JabberConnectionListener connectionListener;
/**
* The details of the proxy we are using to connect to the server (if any)
*/
private org.jivesoftware.smack.proxy.ProxyInfo proxy;
/**
* State for connect and login state.
*/
enum ConnectState
{
/**
* Abort any further connecting.
*/
ABORT_CONNECTING,
/**
* Continue trying with next address.
*/
CONTINUE_TRYING,
/**
* Stop trying we succeeded or just have a final state for
* the whole connecting procedure.
*/
STOP_TRYING
}
/**
* The debugger who logs packets.
*/
private SmackPacketDebugger debugger = null;
/**
* Jingle Nodes service.
*/
private SmackServiceNode jingleNodesServiceNode = null;
/**
* Synchronization object to monitore jingle nodes auto discovery.
*/
private final Object jingleNodesSyncRoot = new Object();
/**
* Stores user credentials for local use if user hasn't stored
* its password.
*/
private UserCredentials userCredentials = null;
/**
* The currently running keepAliveManager if enabled.
*/
private KeepAliveManager keepAliveManager = null;
/**
* The version manager.
*/
private VersionManager versionManager = null;
// load xmpp manager classes
static
{
if(OSUtils.IS_ANDROID)
loadJabberServiceClasses();
}
/**
* An <tt>OperationSet</tt> that allows access to connection information used
* by the protocol provider.
*/
private class OperationSetConnectionInfoJabberImpl
implements OperationSetConnectionInfo
{
/**
* @return The XMPP server address.
*/
@Override
public InetSocketAddress getServerAddress()
{
return address;
}
}
/**
* 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(connection == null)
return RegistrationState.UNREGISTERED;
else if(connection.isConnected() && connection.isAuthenticated())
return RegistrationState.REGISTERED;
else
return RegistrationState.UNREGISTERED;
}
/**
* Return the certificate verification service impl.
* @return the CertificateVerification service.
*/
private CertificateService getCertificateVerificationService()
{
if(guiVerification == null)
{
guiVerification
= ServiceUtils.getService(
JabberActivator.getBundleContext(),
CertificateService.class);
}
return guiVerification;
}
/**
* 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 we'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;
try
{
// reset states
abortConnecting = false;
// indicate we started connectAndLogin process
synchronized(connectAndLoginLock)
{
inConnectAndLogin = true;
}
initializeConnectAndLogin(authority,
SecurityAuthority.AUTHENTICATION_REQUIRED);
}
catch (XMPPException ex)
{
logger.error("Error registering", ex);
eventDuringLogin = null;
fireRegistrationStateChanged(ex);
}
finally
{
synchronized(connectAndLoginLock)
{
// Checks if an error has occurred during login, if so we fire
// it here in order to avoid a deadlock which occurs in
// reconnect plugin. The deadlock is cause we fired an event
// during login process and have locked initializationLock and
// we cannot unregister from reconnect, cause unregister method
// also needs this lock.
if(eventDuringLogin != null)
{
if(eventDuringLogin.getNewState().equals(
RegistrationState.CONNECTION_FAILED) ||
eventDuringLogin.getNewState().equals(
RegistrationState.UNREGISTERED))
disconnectAndCleanConnection();
fireRegistrationStateChanged(
eventDuringLogin.getOldState(),
eventDuringLogin.getNewState(),
eventDuringLogin.getReasonCode(),
eventDuringLogin.getReason());
eventDuringLogin = null;
inConnectAndLogin = false;
return;
}
inConnectAndLogin = false;
}
}
}
/**
* Connects and logins again to the server.
*
* @param authReasonCode indicates the reason of the re-authentication.
*/
void reregister(int authReasonCode)
{
try
{
if (logger.isTraceEnabled())
logger.trace("Trying to reregister us!");
// sets this if any is trying to use us through registration
// to know we are not registered
this.unregisterInternal(false);
// reset states
this.abortConnecting = false;
// indicate we started connectAndLogin process
synchronized(connectAndLoginLock)
{
inConnectAndLogin = true;
}
initializeConnectAndLogin(authority, authReasonCode);
}
catch(OperationFailedException ex)
{
logger.error("Error ReRegistering", ex);
eventDuringLogin = null;
disconnectAndCleanConnection();
fireRegistrationStateChanged(getRegistrationState(),
RegistrationState.CONNECTION_FAILED,
RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, null);
}
catch (XMPPException ex)
{
logger.error("Error ReRegistering", ex);
eventDuringLogin = null;
fireRegistrationStateChanged(ex);
}
finally
{
synchronized(connectAndLoginLock)
{
// Checks if an error has occurred during login, if so we fire
// it here in order to avoid a deadlock which occurs in
// reconnect plugin. The deadlock is cause we fired an event
// during login process and have locked initializationLock and
// we cannot unregister from reconnect, cause unregister method
// also needs this lock.
if(eventDuringLogin != null)
{
if(eventDuringLogin.getNewState().equals(
RegistrationState.CONNECTION_FAILED) ||
eventDuringLogin.getNewState().equals(
RegistrationState.UNREGISTERED))
disconnectAndCleanConnection();
fireRegistrationStateChanged(
eventDuringLogin.getOldState(),
eventDuringLogin.getNewState(),
eventDuringLogin.getReasonCode(),
eventDuringLogin.getReason());
eventDuringLogin = null;
inConnectAndLogin = false;
return;
}
inConnectAndLogin = false;
}
}
}
/**
* Indicates if the XMPP transport channel is using a TLS secured socket.
*
* @return True when TLS is used, false otherwise.
*/
public boolean isSignalingTransportSecure()
{
return connection.isSecureConnection();
}
/**
* 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, TLS or UNKNOWN.
*/
public TransportProtocol getTransportProtocol()
{
// Without a connection, there is no transport available.
if(connection != null && connection.isConnected())
{
// Transport using a secure connection.
if(isSignalingTransportSecure())
{
return TransportProtocol.TLS;
}
// Transport using a unsecure connection.
return TransportProtocol.TCP;
}
return TransportProtocol.UNKNOWN;
}
/**
* Connects and logins to the server
* @param authority SecurityAuthority
* @param reasonCode the authentication reason code. Indicates the reason of
* this authentication.
* @throws XMPPException if we cannot connect to the server - network problem
* @throws OperationFailedException if login parameters
* as server port are not correct
*/
private void initializeConnectAndLogin(SecurityAuthority authority,
int reasonCode)
throws XMPPException, OperationFailedException
{
synchronized(initializationLock)
{
// if a thread is waiting for initializationLock and enters
// lets check whether someone hasn't already tried login and
// have succeeded,
// should prevent "Trace possible duplicate connections" prints
if(isRegistered())
return;
JabberLoginStrategy loginStrategy = createLoginStrategy();
userCredentials = loginStrategy.prepareLogin(authority, reasonCode);
if(!loginStrategy.loginPreparationSuccessful())
return;
String serviceName
= StringUtils.parseServer(getAccountID().getUserID());
loadResource();
loadProxy();
Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
ConnectState state;
//[0] = hadDnsSecException
boolean[] hadDnsSecException = new boolean[]{false};
// try connecting with auto-detection if enabled
boolean isServerOverriden =
getAccountID().getAccountPropertyBoolean(
ProtocolProviderFactory.IS_SERVER_OVERRIDDEN, false);
if(!isServerOverriden)
{
state = connectUsingSRVRecords(serviceName,
serviceName, hadDnsSecException, loginStrategy);
if(hadDnsSecException[0])
{
setDnssecLoginFailure();
return;
}
if(state == ConnectState.ABORT_CONNECTING
|| state == ConnectState.STOP_TRYING)
return;
}
// check for custom xmpp domain which we will check for
// SRV records for server addresses
String customXMPPDomain = getAccountID()
.getAccountPropertyString("CUSTOM_XMPP_DOMAIN");
if(customXMPPDomain != null && !hadDnsSecException[0])
{
logger.info("Connect using custom xmpp domain: " +
customXMPPDomain);
state = connectUsingSRVRecords(
customXMPPDomain, serviceName,
hadDnsSecException, loginStrategy);
logger.info("state for connectUsingSRVRecords: " + state);
if(hadDnsSecException[0])
{
setDnssecLoginFailure();
return;
}
if(state == ConnectState.ABORT_CONNECTING
|| state == ConnectState.STOP_TRYING)
return;
}
// connect with specified server name
String serverAddressUserSetting
= getAccountID().getAccountPropertyString(
ProtocolProviderFactory.SERVER_ADDRESS);
int serverPort = getAccountID().getAccountPropertyInt(
ProtocolProviderFactory.SERVER_PORT, 5222);
InetSocketAddress[] addrs = null;
try
{
addrs = NetworkUtils.getAandAAAARecords(
serverAddressUserSetting,
serverPort
);
}
catch (ParseException e)
{
logger.error("Domain not resolved", e);
}
catch (DnssecException e)
{
logger.error("DNSSEC failure for overridden server", e);
setDnssecLoginFailure();
return;
}
if (addrs == null || addrs.length == 0)
{
logger.error("No server addresses found");
eventDuringLogin = null;
fireRegistrationStateChanged(
getRegistrationState(),
RegistrationState.CONNECTION_FAILED,
RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND,
"No server addresses found");
}
else
{
for (InetSocketAddress isa : addrs)
{
try
{
state = connectAndLogin(isa, serviceName,
loginStrategy);
if(state == ConnectState.ABORT_CONNECTING
|| state == ConnectState.STOP_TRYING)
return;
}
catch(XMPPException ex)
{
disconnectAndCleanConnection();
if(isAuthenticationFailed(ex))
throw ex;
}
}
}
}
}
/**
* Creates the JabberLoginStrategy to use for the current account.
*/
private JabberLoginStrategy createLoginStrategy()
{
if (((JabberAccountIDImpl)getAccountID()).isAnonymousAuthUsed())
{
return new AnonymousLoginStrategy(
getAccountID().getAuthorizationName());
}
String clientCertId = getAccountID().getAccountPropertyString(
ProtocolProviderFactory.CLIENT_TLS_CERTIFICATE);
if(clientCertId != null)
{
return new LoginByClientCertificateStrategy(getAccountID());
}
else
{
return new LoginByPasswordStrategy(this, getAccountID());
}
}
private void setDnssecLoginFailure()
{
eventDuringLogin = new RegistrationStateChangeEvent(
this,
getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST,
"No usable host found due to DNSSEC failures");
}
/**
* Connects using the domain specified and its SRV records.
* @param domain the domain to use
* @param serviceName the domain name of the user's login
* @param dnssecState state of possible received DNSSEC exceptions
* @param loginStrategy the login strategy to use
* @return whether to continue trying or stop.
*/
private ConnectState connectUsingSRVRecords(
String domain,
String serviceName,
boolean[] dnssecState,
JabberLoginStrategy loginStrategy)
throws XMPPException
{
// check to see is there SRV records for this server domain
SRVRecord srvRecords[] = null;
try
{
srvRecords = NetworkUtils
.getSRVRecords("xmpp-client", "tcp", domain);
}
catch (ParseException e)
{
logger.error("SRV record not resolved", e);
}
catch (DnssecException e)
{
logger.error("DNSSEC failure for SRV lookup", e);
dnssecState[0] = true;
}
if(srvRecords != null)
{
for(SRVRecord srv : srvRecords)
{
InetSocketAddress[] addrs = null;
try
{
addrs =
NetworkUtils.getAandAAAARecords(
srv.getTarget(),
srv.getPort()
);
}
catch (ParseException e)
{
logger.error("Invalid SRV record target", e);
}
catch (DnssecException e)
{
logger.error("DNSSEC failure for A/AAAA lookup of SRV", e);
dnssecState[0] = true;
}
if (addrs == null || addrs.length == 0)
{
logger.error("No A/AAAA addresses found for " +
srv.getTarget());
continue;
}
for (InetSocketAddress isa : addrs)
{
try
{
// if failover mechanism is enabled, use it,
// default is not enabled.
if(JabberActivator.getConfigurationService()
.getBoolean(FailoverConnectionMonitor
.REVERSE_FAILOVER_ENABLED_PROP,
false
))
{
FailoverConnectionMonitor.getInstance(this)
.setCurrent(domain, srv.getTarget());
}
ConnectState state = connectAndLogin(
isa, serviceName, loginStrategy);
return state;
}
catch(XMPPException ex)
{
logger.error("Error connecting to " + isa
+ " for domain:" + domain
+ " serviceName:" + serviceName, ex);
disconnectAndCleanConnection();
if(isAuthenticationFailed(ex))
throw ex;
}
}
}
}
else
logger.error("No SRV addresses found for _xmpp-client._tcp."
+ domain);
return ConnectState.CONTINUE_TRYING;
}
/**
* Tries to login to the XMPP server with the supplied user ID. If the
* protocol is Google Talk, the user ID including the service name is used.
* For other protocols, if the login with the user ID without the service
* name fails, a second attempt including the service name is made.
*
* @param currentAddress the IP address to connect to
* @param serviceName the domain name of the user's login
* @param loginStrategy the login strategy to use
* @throws XMPPException when a failure occurs
*/
private ConnectState connectAndLogin(InetSocketAddress currentAddress,
String serviceName,
JabberLoginStrategy loginStrategy)
throws XMPPException
{
String userID = null;
boolean qualifiedUserID;
/* with a google account (either gmail or google apps
* related ones), the userID MUST be the full e-mail address
* not just the ID
*/
if(getAccountID().getProtocolDisplayName().equals("Google Talk"))
{
userID = getAccountID().getUserID();
qualifiedUserID = true;
}
else
{
userID = StringUtils.parseName(getAccountID().getUserID());
qualifiedUserID = false;
}
try
{
return connectAndLogin(
currentAddress, serviceName,
userID, resource, loginStrategy);
}
catch(XMPPException ex)
{
// server disconnect us after such an error, do cleanup
disconnectAndCleanConnection();
//no need to check with a different username if the
//socket could not be opened
if (ex.getWrappedThrowable() instanceof ConnectException
|| ex.getWrappedThrowable() instanceof NoRouteToHostException)
{
//as we got an exception not handled in connectAndLogin
//no state was set, so fire it here so we can continue
//with the re-register process
//2013-08-07 do not fire event, if we have several
// addresses and we fire event will activate reconnect
// but we will continue connecting with other addresses
// and can register with address, then unregister and try again
// that is from reconnect plugin.
// Storing event for fire after all have failed and we have
// tried every address.
eventDuringLogin = new RegistrationStateChangeEvent(
ProtocolProviderServiceJabberImpl.this,
getRegistrationState(),
RegistrationState.CONNECTION_FAILED,
RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND,
null);
throw ex;
}
// don't attempt to append the service name if it's already there
if (!qualifiedUserID)
{
try
{
// logging in might need the service name
return connectAndLogin(
currentAddress, serviceName,
userID + "@" + serviceName,
resource,
loginStrategy);
}
catch(XMPPException ex2)
{
disconnectAndCleanConnection();
throw ex; //throw the original exception
}
}
else
throw ex;
}
}
/**
* Initializes the Jabber Resource identifier.
*/
private void loadResource()
{
if(resource == null)
{
String defaultResource = "jitsi";
String autoGenenerateResource =
getAccountID().getAccountPropertyString(
ProtocolProviderFactory.AUTO_GENERATE_RESOURCE);
if(autoGenenerateResource == null ||
Boolean.parseBoolean(autoGenenerateResource))
{
SecureRandom random = new SecureRandom();
resource = defaultResource + "-" +
new BigInteger(32, random).toString(32);
}
else
{
resource = getAccountID().getAccountPropertyString(
ProtocolProviderFactory.RESOURCE);
if(resource == null || resource.length() == 0)
resource = defaultResource;
}
}
}
/**
* Sets the global proxy information based on the configuration
*
* @throws OperationFailedException
*/
private void loadProxy() throws OperationFailedException
{
String globalProxyType =
JabberActivator.getConfigurationService()
.getString(ProxyInfo.CONNECTION_PROXY_TYPE_PROPERTY_NAME);
if(globalProxyType == null ||
globalProxyType.equals(ProxyInfo.ProxyType.NONE.name()))
{
proxy = org.jivesoftware.smack.proxy.ProxyInfo.forNoProxy();
}
else
{
String globalProxyAddress =
JabberActivator.getConfigurationService().getString(
ProxyInfo.CONNECTION_PROXY_ADDRESS_PROPERTY_NAME);
String globalProxyPortStr =
JabberActivator.getConfigurationService().getString(
ProxyInfo.CONNECTION_PROXY_PORT_PROPERTY_NAME);
int globalProxyPort;
try
{
globalProxyPort = Integer.parseInt(
globalProxyPortStr);
}
catch(NumberFormatException ex)
{
throw new OperationFailedException("Wrong proxy port, "
+ globalProxyPortStr
+ " does not represent an integer",
OperationFailedException.INVALID_ACCOUNT_PROPERTIES,
ex);
}
String globalProxyUsername =
JabberActivator.getConfigurationService().getString(
ProxyInfo.CONNECTION_PROXY_USERNAME_PROPERTY_NAME);
String globalProxyPassword =
JabberActivator.getConfigurationService().getString(
ProxyInfo.CONNECTION_PROXY_PASSWORD_PROPERTY_NAME);
if(globalProxyAddress == null ||
globalProxyAddress.length() <= 0)
{
throw new OperationFailedException(
"Missing Proxy Address",
OperationFailedException.INVALID_ACCOUNT_PROPERTIES);
}
try
{
proxy = new org.jivesoftware.smack.proxy.ProxyInfo(
Enum.valueOf(org.jivesoftware.smack.proxy.ProxyInfo.
ProxyType.class, globalProxyType),
globalProxyAddress, globalProxyPort,
globalProxyUsername, globalProxyPassword);
}
catch(IllegalArgumentException e)
{
logger.error("Invalid value for smack proxy enum", e);
proxy = null;
}
}
}
/**
* Connects xmpp connection and login. Returning the state whether is it
* final - Abort due to certificate cancel or keep trying cause only current
* address has failed or stop trying cause we succeeded.
* @param address the address to connect to
* @param serviceName the service name to use
* @param userName the username to use
* @param resource and the resource.
* @param loginStrategy the login strategy to use
* @return return the state how to continue the connect process.
* @throws XMPPException if we cannot connect for some reason
*/
private ConnectState connectAndLogin(
InetSocketAddress address, String serviceName,
String userName, String resource,
JabberLoginStrategy loginStrategy)
throws XMPPException
{
// BOSH or TCP ?
ConnectionConfiguration confConn;
String boshURL = accountID.getBoshUrl();
boolean isBosh = !org.jitsi.util.StringUtils.isNullOrEmpty(boshURL);
if (isBosh)
{
confConn = new BOSHConfiguration(serviceName);
((BOSHConfiguration)confConn).setBoshUrl(boshURL);
}
else
{
confConn
= new ConnectionConfiguration(
address.getAddress().getHostAddress(),
address.getPort(),
serviceName, proxy);
}
// if we have OperationSetPersistentPresence skip sending initial
// presence while login is executed, the OperationSet will take care
// of it
if(getOperationSet(OperationSetPersistentPresence.class) != null)
confConn.setSendPresence(false);
confConn.setReconnectionAllowed(false);
boolean tlsRequired = loginStrategy.isTlsRequired();
// user have the possibility to disable TLS but in this case, it will
// not be able to connect to a server which requires TLS
confConn.setSecurityMode(
tlsRequired ? ConnectionConfiguration.SecurityMode.required :
ConnectionConfiguration.SecurityMode.enabled);
TLSUtils.setTLSOnly(confConn);
if(connection != null)
{
logger.error("Connection is not null and isConnected:"
+ connection.isConnected(),
new Exception("Trace possible duplicate connections: " +
getAccountID().getAccountAddress()));
disconnectAndCleanConnection();
}
connection
= isBosh
? new XMPPBOSHConnection((BOSHConfiguration)confConn)
: new XMPPConnection(confConn);
this.address = address;
try
{
CertificateService cvs =
getCertificateVerificationService();
if(cvs != null)
{
SSLContext sslContext = loginStrategy.createSslContext(cvs,
getTrustManager(cvs, serviceName));
// log SSL/TLS algorithms and protocols
if (logger.isDebugEnabled() && sslContext != null)
{
final StringBuilder buff = new StringBuilder();
buff.append("Available TLS protocols and algorithms:\n");
buff.append("Default protocols: ");
buff.append(Arrays.toString(
sslContext.getDefaultSSLParameters().getProtocols()));
buff.append("\n");
buff.append("Supported protocols: ");
buff.append(Arrays.toString(
sslContext.getSupportedSSLParameters().getProtocols()));
buff.append("\n");
buff.append("Default cipher suites: ");
buff.append(Arrays.toString(
sslContext.getDefaultSSLParameters()
.getCipherSuites()));
buff.append("\n");
buff.append("Supported cipher suites: ");
buff.append(Arrays.toString(
sslContext.getSupportedSSLParameters()
.getCipherSuites()));
logger.debug(buff.toString());
}
confConn.setCustomSSLContext(sslContext);
}
else if (tlsRequired)
throw new XMPPException(
"Certificate verification service is "
+ "unavailable and TLS is required");
}
catch(GeneralSecurityException e)
{
logger.error("Error creating custom trust manager", e);
throw new XMPPException("Error creating custom trust manager", e);
}
// FIXME rework debugger to work with Connection if possible
if(debugger == null && connection instanceof XMPPConnection)
{
debugger = new SmackPacketDebugger();
// sets the debugger
debugger.setConnection((XMPPConnection) connection);
connection.addPacketListener(debugger, null);
connection.addPacketInterceptor(debugger, null);
}
connection.connect();
setTrafficClass();
if(abortConnecting)
{
abortConnecting = false;
disconnectAndCleanConnection();
return ConnectState.ABORT_CONNECTING;
}
registerServiceDiscoveryManager();
if(connectionListener == null)
{
connectionListener = new JabberConnectionListener();
}
if(!connection.isSecureConnection() && tlsRequired)
{
throw new XMPPException("TLS is required by client");
}
if(!connection.isConnected())
{
// connection is not connected, lets set state to our connection
// as failed seems there is some lag/problem with network
// and this way we will inform for it and later reconnect if needed
// as IllegalStateException that is thrown within
// addConnectionListener is not handled properly
disconnectAndCleanConnection();
logger.error("Connection not established, server not found!");
eventDuringLogin = null;
fireRegistrationStateChanged(getRegistrationState(),
RegistrationState.CONNECTION_FAILED,
RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, null);
return ConnectState.ABORT_CONNECTING;
}
else
{
final SSLSocket sslSocket = getSSLSocket();
if (sslSocket != null)
{
StringBuilder buff = new StringBuilder();
buff.append("Chosen TLS protocol and algorithm:\n")
.append("Protocol: ").append(sslSocket.getSession()
.getProtocol()).append("\n")
.append("Cipher suite: ").append(sslSocket.getSession()
.getCipherSuite());
logger.info(buff.toString());
if (logger.isDebugEnabled())
{
buff = new StringBuilder();
buff.append("Server TLS certificate chain:\n");
try
{
buff.append(Arrays.toString(
sslSocket.getSession().getPeerCertificates()));
}
catch (SSLPeerUnverifiedException ex)
{
buff.append("<unavailable: ")
.append(ex.getLocalizedMessage()).append(">");
}
logger.debug(buff.toString());
}
}
connection.addConnectionListener(connectionListener);
}
if(abortConnecting)
{
abortConnecting = false;
disconnectAndCleanConnection();
return ConnectState.ABORT_CONNECTING;
}
fireRegistrationStateChanged(
getRegistrationState()
, RegistrationState.REGISTERING
, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
, null);
if (!loginStrategy.login(connection, userName, resource))
{
disconnectAndCleanConnection();
eventDuringLogin = null;
fireRegistrationStateChanged(
getRegistrationState(),
// not auth failed, or there would be no info-popup
RegistrationState.CONNECTION_FAILED,
RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED,
loginStrategy.getClass().getName() + " requests abort");
return ConnectState.ABORT_CONNECTING;
}
if(connection.isAuthenticated())
{
eventDuringLogin = null;
fireRegistrationStateChanged(
getRegistrationState(),
RegistrationState.REGISTERED,
RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null);
return ConnectState.STOP_TRYING;
}
else
{
disconnectAndCleanConnection();
eventDuringLogin = null;
fireRegistrationStateChanged(
getRegistrationState()
, RegistrationState.UNREGISTERED
, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
, null);
return ConnectState.CONTINUE_TRYING;
}
}
/**
* Gets the TrustManager that should be used for the specified service
*
* @param serviceName the service name
* @param cvs The CertificateVerificationService to retrieve the
* trust manager
* @return the trust manager
*/
private X509TrustManager getTrustManager(CertificateService cvs,
String serviceName)
throws GeneralSecurityException
{
return new HostTrustManager(
cvs.getTrustManager(
Arrays.asList(new String[]{
serviceName,
"_xmpp-client." + serviceName
})
)
);
}
/**
* Registers our ServiceDiscoveryManager
*/
private void registerServiceDiscoveryManager()
{
// we setup supported features no packets are actually sent
//during feature registration so we'd better do it here so that
//our first presence update would contain a caps with the right
//features.
String name
= System.getProperty(
"sip-communicator.application.name",
"SIP Communicator ")
+ System.getProperty("sip-communicator.version","SVN");
ServiceDiscoveryManager.setIdentityName(name);
ServiceDiscoveryManager.setIdentityType("pc");
discoveryManager
= new ScServiceDiscoveryManager(
this,
connection,
new String[] { "http://jabber.org/protocol/commands"},
// Add features Jitsi supports in addition to smack.
supportedFeatures.toArray(
new String[supportedFeatures.size()]),
true);
/*
* Expose the discoveryManager as service-public through the
* OperationSetContactCapabilities of this ProtocolProviderService.
*/
if (opsetContactCapabilities != null)
opsetContactCapabilities.setDiscoveryManager(discoveryManager);
}
/**
* Used to disconnect current connection and clean it.
*/
public void disconnectAndCleanConnection()
{
if(connection != null)
{
connection.removeConnectionListener(connectionListener);
// disconnect anyway cause it will clear any listeners
// that maybe added even if its not connected
try
{
OperationSetPersistentPresenceJabberImpl opSet =
(OperationSetPersistentPresenceJabberImpl)
this.getOperationSet(OperationSetPersistentPresence.class);
Presence unavailablePresence =
new Presence(Presence.Type.unavailable);
if(opSet != null
&& !org.jitsi.util.StringUtils
.isNullOrEmpty(opSet.getCurrentStatusMessage()))
{
unavailablePresence.setStatus(
opSet.getCurrentStatusMessage());
}
connection.disconnect(unavailablePresence);
} catch (Exception e)
{}
connectionListener = null;
connection = null;
// make it null as it also holds a reference to the old connection
// will be created again on new connection
try
{
/*
* The discoveryManager is exposed as service-public by the
* OperationSetContactCapabilities of this
* ProtocolProviderService. No longer expose it because it's
* going away.
*/
if (opsetContactCapabilities != null)
opsetContactCapabilities.setDiscoveryManager(null);
}
finally
{
if(discoveryManager != null)
{
discoveryManager.stop();
discoveryManager = null;
}
}
}
}
/**
* Ends the registration of this protocol provider with the service.
*/
public void unregister()
{
unregisterInternal(true);
}
/**
* Ends the registration of this protocol provider with the service.
* @param userRequest is the unregister by user request.
*/
public void unregister(boolean userRequest)
{
unregisterInternal(true, userRequest);
}
/**
* Unregister and fire the event if requested
* @param fireEvent boolean
*/
public void unregisterInternal(boolean fireEvent)
{
unregisterInternal(fireEvent, false);
}
/**
* Unregister and fire the event if requested
* @param fireEvent boolean
*/
public void unregisterInternal(boolean fireEvent, boolean userRequest)
{
synchronized(initializationLock)
{
if(fireEvent)
{
eventDuringLogin = null;
fireRegistrationStateChanged(
getRegistrationState()
, RegistrationState.UNREGISTERING
, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
, null
, userRequest);
}
disconnectAndCleanConnection();
RegistrationState currRegState = getRegistrationState();
if(fireEvent)
{
eventDuringLogin = null;
fireRegistrationStateChanged(
currRegState,
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST, null,
userRequest);
}
}
}
/**
* 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()
{
return ProtocolNames.JABBER;
}
/**
* Initialized the service implementation, and puts it in a sate where it
* could interoperate with other services. It is strongly recommended 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,
JabberAccountID accountID)
{
synchronized(initializationLock)
{
this.accountID = accountID;
// in case of modified account, we clear list of supported features
// and every state change listeners, otherwise we can have two
// OperationSet for same feature and it can causes problem (i.e.
// two OperationSetBasicTelephony can launch two ICE negotiations
// (with different ufrag/pwd) and peer will failed call. And
// by the way user will see two dialog for answering/refusing the
// call
supportedFeatures.clear();
this.clearRegistrationStateChangeListener();
this.clearSupportedOperationSet();
String protocolIconPath
= accountID.getAccountPropertyString(
ProtocolProviderFactory.PROTOCOL_ICON_PATH);
if (protocolIconPath == null)
protocolIconPath = "resources/images/protocol/jabber";
jabberIcon = new ProtocolIconJabberImpl(protocolIconPath);
jabberStatusEnum
= JabberStatusEnum.getJabberStatusEnum(protocolIconPath);
//this feature is mandatory to be compliant with Service Discovery
supportedFeatures.add("http://jabber.org/protocol/disco#info");
String keepAliveStrValue
= accountID.getAccountPropertyString(
ProtocolProviderFactory.KEEP_ALIVE_METHOD);
InfoRetreiver infoRetreiver = new InfoRetreiver(this, screenname);
//initialize the presence OperationSet
OperationSetPersistentPresenceJabberImpl persistentPresence =
new OperationSetPersistentPresenceJabberImpl(this, infoRetreiver);
addSupportedOperationSet(
OperationSetPersistentPresence.class,
persistentPresence);
// TODO: add the feature, if any, corresponding to persistent
// presence, if someone knows
// supportedFeatures.add(_PRESENCE_);
//register it once again for those that simply need presence
addSupportedOperationSet(
OperationSetPresence.class,
persistentPresence);
if(accountID.getAccountPropertyString(
ProtocolProviderFactory.ACCOUNT_READ_ONLY_GROUPS) != null)
{
addSupportedOperationSet(
OperationSetPersistentPresencePermissions.class,
new OperationSetPersistentPresencePermissionsJabberImpl(
this));
}
//initialize the IM operation set
OperationSetBasicInstantMessagingJabberImpl basicInstantMessaging =
new OperationSetBasicInstantMessagingJabberImpl(this);
if (keepAliveStrValue == null
|| keepAliveStrValue.equalsIgnoreCase("XEP-0199"))
{
if(keepAliveManager == null)
keepAliveManager = new KeepAliveManager(this);
}
addSupportedOperationSet(
OperationSetBasicInstantMessaging.class,
basicInstantMessaging);
// The http://jabber.org/protocol/xhtml-im feature is included
// already in smack.
addSupportedOperationSet(
OperationSetExtendedAuthorizations.class,
new OperationSetExtendedAuthorizationsJabberImpl(
this,
persistentPresence));
//initialize the Whiteboard operation set
addSupportedOperationSet(
OperationSetWhiteboarding.class,
new OperationSetWhiteboardingJabberImpl(this));
//initialize the typing notifications operation set
addSupportedOperationSet(
OperationSetTypingNotifications.class,
new OperationSetTypingNotificationsJabberImpl(this));
// The http://jabber.org/protocol/chatstates feature implemented in
// OperationSetTypingNotifications is included already in smack.
//initialize the multi user chat operation set
addSupportedOperationSet(
OperationSetMultiUserChat.class,
new OperationSetMultiUserChatJabberImpl(this));
addSupportedOperationSet(
OperationSetJitsiMeetTools.class,
new OperationSetJitsiMeetToolsJabberImpl(this));
addSupportedOperationSet(
OperationSetServerStoredContactInfo.class,
new OperationSetServerStoredContactInfoJabberImpl(
infoRetreiver));
OperationSetServerStoredAccountInfo accountInfo =
new OperationSetServerStoredAccountInfoJabberImpl(this,
infoRetreiver,
screenname);
addSupportedOperationSet(
OperationSetServerStoredAccountInfo.class,
accountInfo);
// Initialize avatar operation set
addSupportedOperationSet(
OperationSetAvatar.class,
new OperationSetAvatarJabberImpl(this, accountInfo));
// initialize the file transfer operation set
addSupportedOperationSet(
OperationSetFileTransfer.class,
new OperationSetFileTransferJabberImpl(this));
addSupportedOperationSet(
OperationSetInstantMessageTransform.class,
new OperationSetInstantMessageTransformImpl());
// Include features we're supporting in addition to the four
// included by smack itself:
// http://jabber.org/protocol/si/profile/file-transfer
// http://jabber.org/protocol/si
// http://jabber.org/protocol/bytestreams
// http://jabber.org/protocol/ibb
supportedFeatures.add("urn:xmpp:thumbs:0");
supportedFeatures.add("urn:xmpp:bob");
// initialize the thumbnailed file factory operation set
addSupportedOperationSet(
OperationSetThumbnailedFileFactory.class,
new OperationSetThumbnailedFileFactoryImpl());
// TODO: this is the "main" feature to advertise when a client
// support muc. We have to add some features for
// specific functionality we support in muc.
// see http://www.xmpp.org/extensions/xep-0045.html
// The http://jabber.org/protocol/muc feature is already included in
// smack.
supportedFeatures.add("http://jabber.org/protocol/muc#rooms");
supportedFeatures.add("http://jabber.org/protocol/muc#traffic");
// RTP HDR extension
supportedFeatures.add(URN_XMPP_JINGLE_RTP_HDREXT);
ProviderManager providerManager
= ProtocolProviderFactoryJabberImpl.providerManager;
//register our jingle provider
providerManager.addIQProvider( JingleIQ.ELEMENT_NAME,
JingleIQ.NAMESPACE,
new JingleIQProvider());
// register our input event provider
providerManager.addIQProvider(InputEvtIQ.ELEMENT_NAME,
InputEvtIQ.NAMESPACE,
new InputEvtIQProvider());
// register our coin provider
providerManager.addIQProvider(CoinIQ.ELEMENT_NAME,
CoinIQ.NAMESPACE,
new CoinIQProvider());
supportedFeatures.add(URN_XMPP_JINGLE_COIN);
// register our JingleInfo provider
providerManager.addIQProvider(JingleInfoQueryIQ.ELEMENT_NAME,
JingleInfoQueryIQ.NAMESPACE,
new JingleInfoQueryIQProvider());
// Jitsi Videobridge IQProvider and PacketExtensionProvider
providerManager.addIQProvider(
ColibriConferenceIQ.ELEMENT_NAME,
ColibriConferenceIQ.NAMESPACE,
new ColibriIQProvider());
providerManager.addIQProvider(
JibriIq.ELEMENT_NAME,
JibriIq.NAMESPACE,
new JibriIqProvider()
);
providerManager.addExtensionProvider(
ConferenceDescriptionPacketExtension.ELEMENT_NAME,
ConferenceDescriptionPacketExtension.NAMESPACE,
new ConferenceDescriptionPacketExtension.Provider());
providerManager.addExtensionProvider(
CarbonPacketExtension.RECEIVED_ELEMENT_NAME,
CarbonPacketExtension.NAMESPACE,
new CarbonPacketExtension.Provider(
CarbonPacketExtension.RECEIVED_ELEMENT_NAME));
providerManager.addExtensionProvider(
CarbonPacketExtension.SENT_ELEMENT_NAME,
CarbonPacketExtension.NAMESPACE,
new CarbonPacketExtension.Provider(
CarbonPacketExtension.SENT_ELEMENT_NAME));
providerManager.addExtensionProvider(
Nick.ELEMENT_NAME,
Nick.NAMESPACE,
new Nick.Provider());
//initialize the telephony operation set
boolean isCallingDisabled
= JabberActivator.getConfigurationService()
.getBoolean(IS_CALLING_DISABLED, false);
boolean isCallingDisabledForAccount
= accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.IS_CALLING_DISABLED_FOR_ACCOUNT,
false);
// Check if calling is enabled.
if (!isCallingDisabled && !isCallingDisabledForAccount)
{
OperationSetBasicTelephonyJabberImpl basicTelephony
= new OperationSetBasicTelephonyJabberImpl(this);
addSupportedOperationSet(
OperationSetAdvancedTelephony.class,
basicTelephony);
addSupportedOperationSet(
OperationSetBasicTelephony.class,
basicTelephony);
addSupportedOperationSet(
OperationSetSecureZrtpTelephony.class,
basicTelephony);
addSupportedOperationSet(
OperationSetSecureSDesTelephony.class,
basicTelephony);
// initialize video telephony OperationSet
addSupportedOperationSet(
OperationSetVideoTelephony.class,
new OperationSetVideoTelephonyJabberImpl(basicTelephony));
addSupportedOperationSet(
OperationSetTelephonyConferencing.class,
new OperationSetTelephonyConferencingJabberImpl(this));
addSupportedOperationSet(
OperationSetBasicAutoAnswer.class,
new OperationSetAutoAnswerJabberImpl(this));
addSupportedOperationSet(
OperationSetResourceAwareTelephony.class,
new OperationSetResAwareTelephonyJabberImpl(basicTelephony));
// Only init video bridge if enabled
boolean isVideobridgeDisabled
= JabberActivator.getConfigurationService()
.getBoolean(OperationSetVideoBridge.
IS_VIDEO_BRIDGE_DISABLED, false);
if (!isVideobridgeDisabled)
{
// init video bridge
addSupportedOperationSet(
OperationSetVideoBridge.class,
new OperationSetVideoBridgeImpl(this));
}
// init DTMF
OperationSetDTMFJabberImpl operationSetDTMF
= new OperationSetDTMFJabberImpl(this);
addSupportedOperationSet(
OperationSetDTMF.class, operationSetDTMF);
addSupportedOperationSet(
OperationSetIncomingDTMF.class,
new OperationSetIncomingDTMFJabberImpl());
addJingleFeatures();
// Check if desktop streaming is enabled.
boolean isDesktopStreamingDisabled
= JabberActivator.getConfigurationService()
.getBoolean(IS_DESKTOP_STREAMING_DISABLED, false);
boolean isAccountDesktopStreamingDisabled
= accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.IS_DESKTOP_STREAMING_DISABLED,
false);
if (!isDesktopStreamingDisabled
&& !isAccountDesktopStreamingDisabled)
{
// initialize desktop streaming OperationSet
addSupportedOperationSet(
OperationSetDesktopStreaming.class,
new OperationSetDesktopStreamingJabberImpl(
basicTelephony));
if(!accountID.getAccountPropertyBoolean(
ProtocolProviderFactory
.IS_DESKTOP_REMOTE_CONTROL_DISABLED,
false))
{
// initialize desktop sharing OperationSets
addSupportedOperationSet(
OperationSetDesktopSharingServer.class,
new OperationSetDesktopSharingServerJabberImpl(
basicTelephony));
// Adds extension to support remote control as a sharing
// server (sharer).
supportedFeatures.add(InputEvtIQ.NAMESPACE_SERVER);
addSupportedOperationSet(
OperationSetDesktopSharingClient.class,
new OperationSetDesktopSharingClientJabberImpl(this)
);
// Adds extension to support remote control as a sharing
// client (sharer).
supportedFeatures.add(InputEvtIQ.NAMESPACE_CLIENT);
}
}
}
// OperationSetContactCapabilities
opsetContactCapabilities
= new OperationSetContactCapabilitiesJabberImpl(this);
if (discoveryManager != null)
opsetContactCapabilities.setDiscoveryManager(discoveryManager);
addSupportedOperationSet(
OperationSetContactCapabilities.class,
opsetContactCapabilities);
addSupportedOperationSet(
OperationSetGenericNotifications.class,
new OperationSetGenericNotificationsJabberImpl(this));
supportedFeatures.add("jabber:iq:version");
if(versionManager == null)
versionManager = new VersionManager(this);
supportedFeatures.add(MessageCorrectionExtension.NAMESPACE);
addSupportedOperationSet(OperationSetMessageCorrection.class,
basicInstantMessaging);
OperationSetChangePassword opsetChangePassword
= new OperationSetChangePasswordJabberImpl(this);
addSupportedOperationSet(OperationSetChangePassword.class,
opsetChangePassword);
OperationSetCusaxUtils opsetCusaxCusaxUtils
= new OperationSetCusaxUtilsJabberImpl(this);
addSupportedOperationSet(OperationSetCusaxUtils.class,
opsetCusaxCusaxUtils);
boolean isUserSearchEnabled = accountID.getAccountPropertyBoolean(
IS_USER_SEARCH_ENABLED_PROPERTY, false);
if(isUserSearchEnabled)
{
addSupportedOperationSet(OperationSetUserSearch.class,
new OperationSetUserSearchJabberImpl(this));
}
OperationSetTLS opsetTLS
= new OperationSetTLSJabberImpl(this);
addSupportedOperationSet(OperationSetTLS.class,
opsetTLS);
OperationSetConnectionInfo opsetConnectionInfo
= new OperationSetConnectionInfoJabberImpl();
addSupportedOperationSet(OperationSetConnectionInfo.class,
opsetConnectionInfo);
isInitialized = true;
}
}
/**
* Adds Jingle related features to the supported features.
*/
private void addJingleFeatures()
{
// Add Jingle features to supported features.
supportedFeatures.add(URN_XMPP_JINGLE);
supportedFeatures.add(URN_XMPP_JINGLE_RTP);
supportedFeatures.add(URN_XMPP_JINGLE_RAW_UDP_0);
/*
* Reflect the preference of the user with respect to the use of
* ICE.
*/
if (accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.IS_USE_ICE,
true))
{
supportedFeatures.add(URN_XMPP_JINGLE_ICE_UDP_1);
}
supportedFeatures.add(URN_XMPP_JINGLE_RTP_AUDIO);
supportedFeatures.add(URN_XMPP_JINGLE_RTP_VIDEO);
supportedFeatures.add(URN_XMPP_JINGLE_RTP_ZRTP);
/*
* Reflect the preference of the user with respect to the use of
* Jingle Nodes.
*/
if (accountID.getAccountPropertyBoolean(
ProtocolProviderFactoryJabberImpl.IS_USE_JINGLE_NODES,
true))
{
supportedFeatures.add(URN_XMPP_JINGLE_NODES);
}
// XEP-0251: Jingle Session Transfer
supportedFeatures.add(URN_XMPP_JINGLE_TRANSFER_0);
// XEP-0320: Use of DTLS-SRTP in Jingle Sessions
if (accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.DEFAULT_ENCRYPTION,
true)
&& accountID.isEncryptionProtocolEnabled(
SrtpControlType.DTLS_SRTP))
{
supportedFeatures.add(URN_XMPP_JINGLE_DTLS_SRTP);
}
}
/**
* 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)
{
if (logger.isTraceEnabled())
logger.trace("Killing the Jabber Protocol Provider.");
//kill all active calls
OperationSetBasicTelephonyJabberImpl telephony
= (OperationSetBasicTelephonyJabberImpl)getOperationSet(
OperationSetBasicTelephony.class);
if (telephony != null)
{
telephony.shutdown();
}
disconnectAndCleanConnection();
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;
}
/**
* Validates the node part of a JID and returns an error message if
* applicable and a suggested correction.
*
* @param contactId the contact identifier to validate
* @param result Must be supplied as an empty a list. Implementors add
* items:
* <ol>
* <li>is the error message if applicable
* <li>a suggested correction. Index 1 is optional and can only
* be present if there was a validation failure.
* </ol>
* @return true if the contact id is valid, false otherwise
*/
@Override
public boolean validateContactAddress(String contactId, List<String> result)
{
if (result == null)
{
throw new IllegalArgumentException("result must be an empty list");
}
result.clear();
try
{
contactId = contactId.trim();
if (contactId.length() == 0)
{
result.add(JabberActivator.getResources().getI18NString(
"impl.protocol.jabber.INVALID_ADDRESS", new String[]
{ contactId }));
// no suggestion for an empty id
return false;
}
String user = contactId;
String remainder = "";
int at = contactId.indexOf('@');
if (at > -1)
{
user = contactId.substring(0, at);
remainder = contactId.substring(at);
}
// <conforming-char> ::= #x21 | [#x23-#x25] | [#x28-#x2E] |
// [#x30-#x39] | #x3B | #x3D | #x3F |
// [#x41-#x7E] | [#x80-#xD7FF] |
// [#xE000-#xFFFD] | [#x10000-#x10FFFF]
boolean valid = true;
String suggestion = "";
for (char c : user.toCharArray())
{
if (!(c == 0x21 || (c >= 0x23 && c <= 0x25)
|| (c >= 0x28 && c <= 0x2e) || (c >= 0x30 && c <= 0x39)
|| c == 0x3b || c == 0x3d || c == 0x3f
|| (c >= 0x41 && c <= 0x7e) || (c >= 0x80 && c <= 0xd7ff)
|| (c >= 0xe000 && c <= 0xfffd)))
{
valid = false;
}
else
{
suggestion += c;
}
}
if (!valid)
{
result.add(JabberActivator.getResources().getI18NString(
"impl.protocol.jabber.INVALID_ADDRESS", new String[]
{ contactId }));
result.add(suggestion + remainder);
return false;
}
return true;
}
catch (Exception ex)
{
result.add(JabberActivator.getResources().getI18NString(
"impl.protocol.jabber.INVALID_ADDRESS", new String[]
{ contactId }));
}
return false;
}
/**
* Returns the <tt>Connection</tt>opened by this provider
* @return a reference to the <tt>Connection</tt> last opened by this
* provider.
*/
public Connection getConnection()
{
return connection;
}
/**
* Determines whether a specific <tt>XMPPException</tt> signals that
* attempted authentication has failed.
*
* @param ex the <tt>XMPPException</tt> which is to be determined whether it
* signals that attempted authentication has failed
* @return <tt>true</tt> if the specified <tt>ex</tt> signals that attempted
* authentication has failed; otherwise, <tt>false</tt>
*/
private boolean isAuthenticationFailed(XMPPException ex)
{
String exMsg = ex.getMessage().toLowerCase();
// as there are no types or reasons for XMPPException
// we try determine the reason according to their message
// all messages that were found in smack 3.1.0 were took in count
return
((exMsg.indexOf("sasl authentication") != -1)
&& (exMsg.indexOf("failed") != -1))
|| (exMsg.indexOf(
"does not support compatible authentication mechanism")
!= -1)
|| (exMsg.indexOf("unable to determine password") != -1);
}
/**
* Tries to determine the appropriate message and status to fire,
* according the exception.
*
* @param ex the {@link XMPPException} that caused the state change.
*/
private void fireRegistrationStateChanged(XMPPException ex)
{
int reason = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED;
RegistrationState regState = RegistrationState.UNREGISTERED;
String reasonStr = null;
Throwable wrappedEx = ex.getWrappedThrowable();
if(wrappedEx != null
&& (wrappedEx instanceof UnknownHostException
|| wrappedEx instanceof ConnectException
|| wrappedEx instanceof SocketException))
{
reason = RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND;
regState = RegistrationState.CONNECTION_FAILED;
}
else
{
String exMsg = ex.getMessage().toLowerCase();
// as there are no types or reasons for XMPPException
// we try determine the reason according to their message
// all messages that were found in smack 3.1.0 were took in count
if(isAuthenticationFailed(ex))
{
JabberActivator.getProtocolProviderFactory().
storePassword(getAccountID(), null);
reason = RegistrationStateChangeEvent
.REASON_AUTHENTICATION_FAILED;
regState = RegistrationState.AUTHENTICATION_FAILED;
fireRegistrationStateChanged(
getRegistrationState(), regState, reason, null);
// Try to reregister and to ask user for a new password.
reregister(SecurityAuthority.WRONG_PASSWORD);
return;
}
else if(exMsg.indexOf("no response from the server") != -1
|| exMsg.indexOf("connection failed") != -1)
{
reason = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED;
regState = RegistrationState.CONNECTION_FAILED;
}
else if(exMsg.indexOf("tls is required") != -1)
{
regState = RegistrationState.AUTHENTICATION_FAILED;
reason = RegistrationStateChangeEvent.REASON_TLS_REQUIRED;
}
}
if(regState == RegistrationState.UNREGISTERED
|| regState == RegistrationState.CONNECTION_FAILED)
{
// we fired that for some reason we are going offline
// lets clean the connection state for any future connections
disconnectAndCleanConnection();
}
fireRegistrationStateChanged(
getRegistrationState(), regState, reason, reasonStr);
}
/**
* Enable to listen for jabber connection events
*/
private class JabberConnectionListener
implements ConnectionListener
{
/**
* Implements <tt>connectionClosed</tt> from <tt>ConnectionListener</tt>
*/
public void connectionClosed()
{
// if we are in the middle of connecting process
// do not fire events, will do it later when the method
// connectAndLogin finishes its work
synchronized(connectAndLoginLock)
{
if(inConnectAndLogin)
{
eventDuringLogin = new RegistrationStateChangeEvent(
ProtocolProviderServiceJabberImpl.this,
getRegistrationState(),
RegistrationState.CONNECTION_FAILED,
RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
null);
return;
}
}
// fire that a connection failed, the reconnection mechanism
// will look after us and will clean us, other wise we can do
// a dead lock (connection closed is called
// within xmppConneciton and calling disconnect again can lock it)
fireRegistrationStateChanged(
getRegistrationState(),
RegistrationState.CONNECTION_FAILED,
RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
null);
}
/**
* Implements <tt>connectionClosedOnError</tt> from
* <tt>ConnectionListener</tt>.
*
* @param exception contains information on the error.
*/
public void connectionClosedOnError(Exception exception)
{
logger.error("connectionClosedOnError " +
exception.getLocalizedMessage(), exception);
int reason = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED;
if(exception instanceof XMPPException)
{
StreamError err = ((XMPPException)exception).getStreamError();
if(err != null && err.getCode().equals(
XMPPError.Condition.conflict.toString()))
{
// if we are in the middle of connecting process
// do not fire events, will do it later when the method
// connectAndLogin finishes its work
synchronized(connectAndLoginLock)
{
if(inConnectAndLogin)
{
eventDuringLogin = new RegistrationStateChangeEvent(
ProtocolProviderServiceJabberImpl.this,
getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_MULTIPLE_LOGINS,
"Connecting multiple times with the same resource");
return;
}
}
disconnectAndCleanConnection();
fireRegistrationStateChanged(getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_MULTIPLE_LOGINS,
"Connecting multiple times with the same resource");
return;
}
} // Ignore certificate exceptions as we handle them elsewhere
else if(exception instanceof SSLHandshakeException &&
exception.getCause() instanceof CertificateException)
{
return;
}
else if(exception instanceof XmlPullParserException)
{
reason = RegistrationStateChangeEvent
.REASON_SERVER_RETURNED_ERRONEOUS_INPUT;
}
// if we are in the middle of connecting process
// do not fire events, will do it later when the method
// connectAndLogin finishes its work
synchronized(connectAndLoginLock)
{
if(inConnectAndLogin)
{
eventDuringLogin = new RegistrationStateChangeEvent(
ProtocolProviderServiceJabberImpl.this,
getRegistrationState(),
RegistrationState.CONNECTION_FAILED,
reason,
exception.getMessage());
return;
}
}
disconnectAndCleanConnection();
fireRegistrationStateChanged(getRegistrationState(),
RegistrationState.CONNECTION_FAILED,
reason,
exception.getMessage());
}
/**
* Implements <tt>reconnectingIn</tt> from <tt>ConnectionListener</tt>
*
* @param i delay in seconds for reconnection.
*/
public void reconnectingIn(int i)
{
if (logger.isInfoEnabled())
logger.info("reconnectingIn " + i);
}
/**
* Implements <tt>reconnectingIn</tt> from <tt>ConnectionListener</tt>
*/
public void reconnectionSuccessful()
{
if (logger.isInfoEnabled())
logger.info("reconnectionSuccessful");
}
/**
* Implements <tt>reconnectionFailed</tt> from
* <tt>ConnectionListener</tt>.
*
* @param exception description of the failure
*/
public void reconnectionFailed(Exception exception)
{
if (logger.isInfoEnabled())
logger.info("reconnectionFailed ", exception);
}
}
/**
* Returns the jabber protocol icon.
* @return the jabber protocol icon
*/
public ProtocolIcon getProtocolIcon()
{
return jabberIcon;
}
/**
* Returns the current instance of <tt>JabberStatusEnum</tt>.
*
* @return the current instance of <tt>JabberStatusEnum</tt>.
*/
JabberStatusEnum getJabberStatusEnum()
{
return jabberStatusEnum;
}
/**
* Determines if the given list of <tt>features</tt> is supported by the
* specified jabber id.
*
* @param jid the jabber id for which to check
* @param features the list of features to check for
*
* @return <tt>true</tt> if the list of features is supported; otherwise,
* <tt>false</tt>
*/
public boolean isFeatureListSupported(String jid, String... features)
{
try
{
if(discoveryManager == null)
return false;
DiscoverInfo featureInfo =
discoveryManager.discoverInfoNonBlocking(jid);
if(featureInfo == null)
return false;
for (String feature : features)
{
if (!featureInfo.containsFeature(feature))
{
// If one is not supported we return false and don't check
// the others.
return false;
}
}
return true;
}
catch (XMPPException e)
{
if (logger.isDebugEnabled())
logger.debug("Failed to retrive discovery info.", e);
}
return false;
}
/**
* Determines if the given list of <tt>features</tt> is supported by the
* specified jabber id.
*
* @param jid the jabber id that we'd like to get information about
* @param feature the feature to check for
*
* @return <tt>true</tt> if the list of features is supported, otherwise
* returns <tt>false</tt>
*/
public boolean isFeatureSupported(String jid, String feature)
{
return isFeatureListSupported(jid, feature);
}
/**
* Returns the full jabber id (jid) corresponding to the given contact. If
* the provider is not connected returns null.
*
* @param contact the contact, for which we're looking for a jid
* @return the jid of the specified contact or null if the provider is not
* yet connected;
*/
public String getFullJid(Contact contact)
{
return getFullJid(contact.getAddress());
}
/**
* Returns the full jabber id (jid) corresponding to the given bare jid. If
* the provider is not connected returns null.
*
* @param bareJid the bare contact address (i.e. no resource) whose full
* jid we are looking for.
* @return the jid of the specified contact or null if the provider is not
* yet connected;
*/
public String getFullJid(String bareJid)
{
Connection connection = getConnection();
// when we are not connected there is no full jid
if (connection != null && connection.isConnected())
{
Roster roster = connection.getRoster();
if (roster != null)
return roster.getPresence(bareJid).getFrom();
}
return null;
}
/**
* The trust manager which asks the client whether to trust particular
* certificate which is not globally trusted.
*/
private class HostTrustManager
implements X509TrustManager
{
/**
* The default trust manager.
*/
private final X509TrustManager tm;
/**
* Creates the custom trust manager.
* @param tm the default trust manager.
*/
HostTrustManager(X509TrustManager tm)
{
this.tm = tm;
}
/**
* Not used.
*
* @return nothing.
*/
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[0];
}
/**
* Not used.
* @param chain the cert chain.
* @param authType authentication type like: RSA.
* @throws CertificateException never
* @throws UnsupportedOperationException always
*/
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException, UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
/**
* Check whether a certificate is trusted, if not as user whether he
* trust it.
* @param chain the certificate chain.
* @param authType authentication type like: RSA.
* @throws CertificateException not trusted.
*/
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
abortConnecting = true;
try
{
tm.checkServerTrusted(chain, authType);
}
catch(CertificateException e)
{
// notify in a separate thread to avoid a deadlock when a
// reg state listener accesses a synchronized XMPPConnection
// method (like getRoster)
new Thread(new Runnable()
{
public void run()
{
fireRegistrationStateChanged(getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST,
"Not trusted certificate");
}
}).start();
throw e;
}
if(abortConnecting)
{
// connect hasn't finished we will continue normally
abortConnecting = false;
return;
}
else
{
// in this situation connect method has finished
// and it was disconnected so we wont to connect.
// register.connect in new thread so we can release the
// current connecting thread, otherwise this blocks
// jabber
new Thread(new Runnable()
{
public void run()
{
reregister(SecurityAuthority.CONNECTION_FAILED);
}
}).start();
return;
}
}
}
/**
* Returns the currently valid {@link ScServiceDiscoveryManager}.
*
* @return the currently valid {@link ScServiceDiscoveryManager}.
*/
public ScServiceDiscoveryManager getDiscoveryManager()
{
return discoveryManager;
}
/**
* Returns our own Jabber ID.
*
* @return our own Jabber ID.
*/
public String getOurJID()
{
String jid = null;
if (connection != null)
jid = connection.getUser();
if (jid == null)
{
// seems like the connection is not yet initialized so lets try to
// construct our jid ourselves.
String accountIDUserID = getAccountID().getUserID();
String userID = StringUtils.parseName(accountIDUserID);
String serviceName = StringUtils.parseServer(accountIDUserID);
jid = userID + "@" + serviceName;
}
return jid;
}
/**
* Returns the <tt>InetAddress</tt> that is most likely to be to be used
* as a next hop when contacting our XMPP server. This is an utility method
* that is used whenever we have to choose one of our local addresses (e.g.
* when trying to pick a best candidate for raw udp). It is based on the
* assumption that, in absence of any more specific details, chances are
* that we will be accessing remote destinations via the same interface
* that we are using to access our jabber server.
*
* @return the <tt>InetAddress</tt> that is most likely to be to be used
* as a next hop when contacting our server.
*
* @throws IllegalArgumentException if we don't have a valid server.
*/
public InetAddress getNextHop()
throws IllegalArgumentException
{
InetAddress nextHop = null;
String nextHopStr = null;
if ( proxy != null
&& proxy.getProxyType()
!= org.jivesoftware.smack.proxy.ProxyInfo.ProxyType.NONE)
{
nextHopStr = proxy.getProxyAddress();
}
else
{
nextHopStr = getConnection().getHost();
}
try
{
nextHop = NetworkUtils.getInetAddress(nextHopStr);
}
catch (UnknownHostException ex)
{
throw new IllegalArgumentException(
"seems we don't have a valid next hop.", ex);
}
if(logger.isDebugEnabled())
logger.debug("Returning address " + nextHop + " as next hop.");
return nextHop;
}
/**
* Start auto-discovery of JingleNodes tracker/relays.
*/
public void startJingleNodesDiscovery()
{
if (!(connection instanceof XMPPConnection))
{
logger.warn(
"Jingle node discovery currently will work only with " +
"TCP XMPP connection");
return;
}
// Jingle Nodes Service Initialization
final XMPPConnection xmppConnection = (XMPPConnection) connection;
final JabberAccountIDImpl accID = (JabberAccountIDImpl)getAccountID();
final SmackServiceNode service
= new SmackServiceNode(xmppConnection, 60000);
// make sure SmackServiceNode will clean up when connection is closed
xmppConnection.addConnectionListener(service);
for(JingleNodeDescriptor desc : accID.getJingleNodes())
{
TrackerEntry entry = new TrackerEntry(
desc.isRelaySupported() ? TrackerEntry.Type.relay :
TrackerEntry.Type.tracker,
TrackerEntry.Policy._public,
desc.getJID(),
JingleChannelIQ.UDP);
service.addTrackerEntry(entry);
}
new Thread(new JingleNodesServiceDiscovery(
service,
xmppConnection,
accID,
jingleNodesSyncRoot))
.start();
jingleNodesServiceNode = service;
}
/**
* Get the Jingle Nodes service. Note that this method will block until
* Jingle Nodes auto discovery (if enabled) finished.
*
* @return Jingle Nodes service
*/
public SmackServiceNode getJingleNodesServiceNode()
{
synchronized(jingleNodesSyncRoot)
{
return jingleNodesServiceNode;
}
}
/**
* Logs a specific message and associated <tt>Throwable</tt> cause as an
* error using the current <tt>Logger</tt> and then throws a new
* <tt>OperationFailedException</tt> with the message, a specific error code
* and the cause.
*
* @param message the message to be logged and then wrapped in a new
* <tt>OperationFailedException</tt>
* @param errorCode the error code to be assigned to the new
* <tt>OperationFailedException</tt>
* @param cause the <tt>Throwable</tt> that has caused the necessity to log
* an error and have a new <tt>OperationFailedException</tt> thrown
* @param logger the logger that we'd like to log the error <tt>message</tt>
* and <tt>cause</tt>.
*
* @throws OperationFailedException the exception that we wanted this method
* to throw.
*/
public static void throwOperationFailedException( String message,
int errorCode,
Throwable cause,
Logger logger)
throws OperationFailedException
{
logger.error(message, cause);
if(cause == null)
throw new OperationFailedException(message, errorCode);
else
throw new OperationFailedException(message, errorCode, cause);
}
/**
* Used when we need to re-register or someone needs to obtain credentials.
* @return the SecurityAuthority.
*/
public SecurityAuthority getAuthority()
{
return authority;
}
UserCredentials getUserCredentials()
{
return userCredentials;
}
/**
* Returns true if our account is a Gmail or a Google Apps ones.
*
* @return true if our account is a Gmail or a Google Apps ones.
*/
public boolean isGmailOrGoogleAppsAccount()
{
String domain = StringUtils.parseServer(
getAccountID().getUserID());
return isGmailOrGoogleAppsAccount(domain);
}
/**
* Returns true if our account is a Gmail or a Google Apps ones.
*
* @param domain domain to check
* @return true if our account is a Gmail or a Google Apps ones.
*/
public static boolean isGmailOrGoogleAppsAccount(String domain)
{
SRVRecord srvRecords[] = null;
try
{
srvRecords = NetworkUtils.getSRVRecords("xmpp-client", "tcp",
domain);
}
catch (ParseException e)
{
logger.info("Failed to get SRV records for XMPP domain");
return false;
}
catch (DnssecException e)
{
logger.error("DNSSEC failure while checking for google domains", e);
return false;
}
if(srvRecords == null)
{
return false;
}
for(SRVRecord srv : srvRecords)
{
if(srv.getTarget().endsWith("google.com") ||
srv.getTarget().endsWith("google.com."))
{
return true;
}
}
return false;
}
/**
* Sets the traffic class for the XMPP signalling socket.
*/
private void setTrafficClass()
{
Socket s = getSocket();
if(s != null)
{
ConfigurationService configService =
JabberActivator.getConfigurationService();
String dscp = configService.getString(XMPP_DSCP_PROPERTY);
if(dscp != null)
{
try
{
int dscpInt = Integer.parseInt(dscp) << 2;
if(dscpInt > 0)
s.setTrafficClass(dscpInt);
}
catch (Exception e)
{
logger.info("Failed to set trafficClass", e);
}
}
}
}
/**
* Gets the entity ID of the first Jitsi Videobridge associated with
* {@link #connection} i.e. provided by the <tt>serviceName</tt> of
* <tt>connection</tt>.
*
* @return the entity ID of the first Jitsi Videobridge associated with
* <tt>connection</tt>
*/
public String getJitsiVideobridge()
{
Connection connection = getConnection();
if (connection != null)
{
ScServiceDiscoveryManager discoveryManager = getDiscoveryManager();
String serviceName = connection.getServiceName();
DiscoverItems discoverItems = null;
try
{
discoverItems = discoveryManager.discoverItems(serviceName);
}
catch (XMPPException xmppe)
{
if (logger.isDebugEnabled())
{
logger.debug(
"Failed to discover the items associated with"
+ " Jabber entity: " + serviceName,
xmppe);
}
}
if (discoverItems != null)
{
Iterator<DiscoverItems.Item> discoverItemIter
= discoverItems.getItems();
while (discoverItemIter.hasNext())
{
DiscoverItems.Item discoverItem = discoverItemIter.next();
String entityID = discoverItem.getEntityID();
DiscoverInfo discoverInfo = null;
try
{
discoverInfo = discoveryManager.discoverInfo(entityID);
}
catch (XMPPException xmppe)
{
logger.warn(
"Failed to discover information about Jabber"
+ " entity: " + entityID,
xmppe);
}
if ((discoverInfo != null)
&& discoverInfo.containsFeature(
ColibriConferenceIQ.NAMESPACE))
{
return entityID;
}
}
}
}
return null;
}
/**
* Load jabber service class, their static context will register
* what is needed. Used in android as when using the other jars
* these services are loaded from the jar manifest.
*/
private static void loadJabberServiceClasses()
{
if(!OSUtils.IS_ANDROID)
return;
try
{
// pre-configure smack in android
// just to load class to init their static blocks
SmackConfiguration.getVersion();
Class.forName(ServiceDiscoveryManager.class.getName());
Class.forName(DelayInformation.class.getName());
Class.forName(org.jivesoftware.smackx
.provider.DelayInformationProvider.class.getName());
Class.forName(org.jivesoftware.smackx
.bytestreams.socks5.Socks5BytestreamManager.class.getName());
Class.forName(XHTMLManager.class.getName());
Class.forName(org.jivesoftware.smackx
.bytestreams.ibb.InBandBytestreamManager.class.getName());
}
catch(ClassNotFoundException e)
{
logger.error("Error loading classes in smack", e);
}
}
/**
* Obtains XMPP connection's socket.
* @return <tt>Socket</tt> instance used by the underlying XMPP connection
* or <tt>null</tt> if "non socket" type of transport is currently used.
*/
private Socket getSocket()
{
return connection != null ? connection.getSocket() : null;
}
/**
* Return the SSL socket (if TLS used).
* @return The SSL socket or null if not used
*/
SSLSocket getSSLSocket()
{
final Socket socket = getSocket();
return (socket instanceof SSLSocket) ? (SSLSocket) socket : null;
}
}