/*
* 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 net.java.sip.communicator.impl.protocol.jabber.sasl.*;
import net.java.sip.communicator.service.certificate.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import org.jivesoftware.smack.*;
import javax.net.ssl.*;
import java.security.*;
/**
* Login to Jabber using username & password.
*
* @author Stefan Sieber
*/
public class LoginByPasswordStrategy
implements JabberLoginStrategy
{
private final AbstractProtocolProviderService protocolProvider;
private final AccountID accountID;
private String password;
/**
* Disables use of custom digest md5 per account.
*/
private String DISABLE_CUSTOM_DIGEST_MD5_ACCOUNT_PROP =
"DISABLE_CUSTOM_DIGEST_MD5";
/**
* Disables use of custom digest md5.
*/
private String DISABLE_CUSTOM_DIGEST_MD5_CONFIG_PROP =
"net.java.sip.communicator.impl.protocol" +
".jabber.DISABLE_CUSTOM_DIGEST_MD5";
/**
* SASLAuthentication currently supports only global mechanisms setting
* so per account property is not of much use, but will keep it for
* compatibility with old versions.
* We try to modify it only once or some concurrent modification exceptions
* can occur.
*/
private static boolean saslMechanismsInitialized = false;
/**
* Used to lock the modifying mechanisms and any login that can occur.
*/
private static Object modifySASLMechanisms = new Object();
/**
* Create a login strategy that logs in using user credentials (username
* and password)
* @param protocolProvider protocol provider service to fire registration
* change events.
* @param accountID The accountID to use for the login.
*/
public LoginByPasswordStrategy(
AbstractProtocolProviderService protocolProvider,
AccountID accountID)
{
this.protocolProvider = protocolProvider;
this.accountID = accountID;
}
/**
* Loads the account passwords as preparation for the login.
*
* @param authority SecurityAuthority to obtain the password
* @param reasonCode reason why we're preparing for login
* @return UserCredentials in case they need to be cached for this session
* (i.e. password is not persistent)
*/
public UserCredentials prepareLogin(SecurityAuthority authority,
int reasonCode)
{
return loadPassword(authority, reasonCode);
}
/**
* Determines whether the strategy is ready to perform the login.
*
* @return True when the password was sucessfully loaded.
*/
public boolean loginPreparationSuccessful()
{
return password != null;
}
/**
* Performs the login on an XMPP connection using SASL PLAIN.
*
* @param connection The connection on which the login is performed.
* @param userName The username for the login.
* @param resource The XMPP resource.
* @return always true.
* @throws XMPPException
*/
public boolean login(Connection connection, String userName,
String resource)
throws XMPPException
{
synchronized(modifySASLMechanisms)
{
boolean disableCustomDigestMD5PerAccount
= accountID.getAccountPropertyBoolean(
DISABLE_CUSTOM_DIGEST_MD5_ACCOUNT_PROP,
false);
if(!saslMechanismsInitialized || disableCustomDigestMD5PerAccount)
{
SASLAuthentication.supportSASLMechanism("PLAIN", 0);
// Insert our sasl mechanism implementation
// in order to support some incompatible servers
boolean disableCustomDigestMD5
= disableCustomDigestMD5PerAccount
|| JabberActivator.getConfigurationService().getBoolean(
DISABLE_CUSTOM_DIGEST_MD5_CONFIG_PROP, false);
if(!disableCustomDigestMD5)
{
SASLAuthentication.unregisterSASLMechanism("DIGEST-MD5");
SASLAuthentication.registerSASLMechanism("DIGEST-MD5",
SASLDigestMD5Mechanism.class);
SASLAuthentication.supportSASLMechanism("DIGEST-MD5");
}
saslMechanismsInitialized = true;
}
}
synchronized(modifySASLMechanisms)
{
connection.login(userName, password, resource);
}
return true;
}
/*
* (non-Javadoc)
*
* @see net.java.sip.communicator.impl.protocol.jabber.JabberLoginStrategy#
* isTlsRequired()
*/
public boolean isTlsRequired()
{
// requires TLS by default (i.e. it will not connect to a non-TLS server
// and will not fallback to cleartext)
return !accountID.getAccountPropertyBoolean(
ProtocolProviderFactory.IS_ALLOW_NON_SECURE, false);
}
/**
* Prepares an SSL Context that is customized SSL context.
*
* @param cs The certificate service that provides the context.
* @param trustManager The TrustManager to use within the context.
* @return An initialized context for the current provider.
* @throws GeneralSecurityException
*/
public SSLContext createSslContext(CertificateService cs,
X509TrustManager trustManager)
throws GeneralSecurityException
{
return cs.getSSLContext(trustManager);
}
/**
* Load the password from the account configuration or ask the user.
*
* @param authority SecurityAuthority
* @param reasonCode the authentication reason code. Indicates the reason of
* this authentication.
* @return The UserCredentials in case they should be cached for this
* session (i.e. are not persistent)
*/
private UserCredentials loadPassword(SecurityAuthority authority,
int reasonCode)
{
UserCredentials cachedCredentials = null;
//verify whether a password has already been stored for this account
password = JabberActivator.
getProtocolProviderFactory().loadPassword(accountID);
//decode
if (password == null)
{
//create a default credentials object
UserCredentials credentials = new UserCredentials();
credentials.setUserName(accountID.getUserID());
//request a password from the user
credentials = authority.obtainCredentials(
accountID.getDisplayName(),
credentials,
reasonCode);
// in case user has canceled the login window
if(credentials == null)
{
protocolProvider.fireRegistrationStateChanged(
protocolProvider.getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST,
"No credentials provided");
return null;
}
//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)
{
protocolProvider.fireRegistrationStateChanged(
protocolProvider.getRegistrationState(),
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_USER_REQUEST,
"No password entered");
return null;
}
password = new String(pass);
if (credentials.isPasswordPersistent())
{
JabberActivator.getProtocolProviderFactory()
.storePassword(accountID, password);
}
else
cachedCredentials = credentials;
}
return cachedCredentials;
}
}