/* * 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.irc; import java.net.*; import net.java.sip.communicator.impl.protocol.irc.ClientConfigImpl.SASLImpl; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jitsi.service.configuration.*; /** * An IRC implementation of the ProtocolProviderService. * * @author Loic Kempf * @author Stephane Remy * @author Danny van Heumen */ public class ProtocolProviderServiceIrcImpl extends AbstractProtocolProviderService { /** * The default secure IRC server port. */ private static final int DEFAULT_SECURE_IRC_PORT = 6697; /** * Logger. */ private static final Logger LOGGER = Logger.getLogger(ProtocolProviderServiceIrcImpl.class); /** * The irc server. */ private IrcStack ircstack = null; /** * The id of the account that this protocol provider represents. */ private AccountID accountID = null; /** * We use this to lock access to initialization. */ private final Object initializationLock = new Object(); /** * The operation set managing multi user chat. */ private OperationSetMultiUserChatIrcImpl multiUserChat; /** * The operation set for instant messaging. */ private OperationSetBasicInstantMessagingIrcImpl instantMessaging; /** * The operation set for persistent presence. */ private OperationSetPersistentPresenceIrcImpl persistentPresence; /** * Indicates whether or not the provider is initialized and ready for use. */ private boolean isInitialized = false; /** * The icon corresponding to the irc protocol. */ private final ProtocolIconIrcImpl ircIcon = new ProtocolIconIrcImpl(); /** * Keeps our current registration state. */ private RegistrationState currentRegistrationState = RegistrationState.UNREGISTERED; /** * The default constructor for the IRC protocol provider. */ public ProtocolProviderServiceIrcImpl() { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Creating a irc provider."); } } /** * Initializes the service implementation, and puts it in a state where it * could operate with other services. It is strongly recommended that * properties in this Map be mapped to property names as specified by * AccountProperties. * * @param userID the user id of the IRC account we're currently * initializing * @param accountID the identifier of the account that this protocol * provider represents. * * @see net.java.sip.communicator.service.protocol.AccountID */ protected void initialize(final String userID, final AccountID accountID) { synchronized (initializationLock) { this.accountID = accountID; //Initialize instant message transform support. addSupportedOperationSet(OperationSetInstantMessageTransform.class, new OperationSetInstantMessageTransformImpl()); //Initialize the multi user chat support multiUserChat = new OperationSetMultiUserChatIrcImpl(this); addSupportedOperationSet( OperationSetMultiUserChat.class, multiUserChat); // Initialize basic instant messaging this.instantMessaging = new OperationSetBasicInstantMessagingIrcImpl(this); // Register basic instant messaging support. addSupportedOperationSet(OperationSetBasicInstantMessaging.class, this.instantMessaging); // Register basic instant messaging transport support. addSupportedOperationSet( OperationSetBasicInstantMessagingTransport.class, this.instantMessaging); //Initialize persistent presence persistentPresence = new OperationSetPersistentPresenceIrcImpl(this); // Register persistent presence support. addSupportedOperationSet(OperationSetPersistentPresence.class, persistentPresence); // Also register for (simple) presence support. addSupportedOperationSet(OperationSetPresence.class, persistentPresence); // TODO Implement OperationSetChangePassword and channel password // changes to IRC remote identity services such as NickServ final String user = getAccountID().getUserID(); ircstack = new IrcStack(this, user, user, "Jitsi", user); isInitialized = true; } } /** * Get the Multi User Chat implementation. * * @return returns the Multi User Chat implementation */ public OperationSetMultiUserChatIrcImpl getMUC() { return this.multiUserChat; } /** * Get the Basic Instant Messaging implementation. * * @return returns the Basic Instant Messaging implementation */ public OperationSetBasicInstantMessagingIrcImpl getBasicInstantMessaging() { return this.instantMessaging; } /** * Get the Persistent Presence implementation. * * @return returns the Persistent Presence implementation. */ public OperationSetPersistentPresenceIrcImpl getPersistentPresence() { return this.persistentPresence; } /** * Returns the AccountID that uniquely identifies the account represented * by this instance of the ProtocolProviderService. * * @return the id of the account represented by this provider. */ public AccountID getAccountID() { return accountID; } /** * Returns the 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 implementing (most often that would be a name in * ProtocolNames). */ public String getProtocolName() { return ProtocolNames.IRC; } /** * Returns the state of the registration of this protocol provider with * the corresponding registration service. * * @return ProviderRegistrationState */ public RegistrationState getRegistrationState() { return currentRegistrationState; } /** * Starts the registration process. * * @param authority the security authority that will be used for * resolving any security challenges that may be returned during the * registration or at any moment while wer're registered. * @throws OperationFailedException with the corresponding code it the * registration fails for some reason (e.g. a networking error or an * implementation problem). */ public void register(final SecurityAuthority authority) throws OperationFailedException { AccountID accountID = getAccountID(); String serverAddress = accountID .getAccountPropertyString( ProtocolProviderFactory.SERVER_ADDRESS); int serverPort = accountID .getAccountPropertyInt( ProtocolProviderFactory.SERVER_PORT, DEFAULT_SECURE_IRC_PORT); //Verify whether a password has already been stored for this account String serverPassword = IrcActivator. getProtocolProviderFactory().loadPassword(getAccountID()); boolean autoNickChange = accountID.getAccountPropertyBoolean( ProtocolProviderFactory.AUTO_CHANGE_USER_NAME, true); boolean resolveDnsThroughProxy = accountID.getAccountPropertyBoolean( ProtocolProviderFactoryIrcImpl.RESOLVE_DNS_THROUGH_PROXY, true); boolean passwordRequired = !accountID.getAccountPropertyBoolean( ProtocolProviderFactory.NO_PASSWORD_REQUIRED, true); boolean secureConnection = accountID.getAccountPropertyBoolean( ProtocolProviderFactory.DEFAULT_ENCRYPTION, true); boolean channelPresenceTask = accountID.getAccountPropertyBoolean( ProtocolProviderFactoryIrcImpl.CHAT_ROOM_PRESENCE_TASK, true); boolean contactPresenceTask = accountID.getAccountPropertyBoolean( ProtocolProviderFactoryIrcImpl.CONTACT_PRESENCE_TASK, true); boolean saslEnabled = accountID.getAccountPropertyBoolean( ProtocolProviderFactoryIrcImpl.SASL_ENABLED, false); String saslUser = accountID.getAccountPropertyString( ProtocolProviderFactoryIrcImpl.SASL_USERNAME); String saslRole = accountID.getAccountPropertyString( ProtocolProviderFactoryIrcImpl.SASL_ROLE); //if we don't - retrieve it from the user through the security authority if (serverPassword == null && passwordRequired) { //create a default credentials object UserCredentials credentials = new UserCredentials(); credentials.setUserName(getAccountID().getUserID()); //request a password from the user credentials = authority.obtainCredentials( ProtocolNames.IRC, credentials, SecurityAuthority.AUTHENTICATION_REQUIRED); char[] pass = null; if (credentials != null) { // extract the password the user passed us. pass = credentials.getPassword(); } // the user didn't provide us a password (canceled the operation) if (pass == null) { fireRegistrationStateChanged( getRegistrationState(), RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_USER_REQUEST, ""); return; } serverPassword = new String(pass); //if the user indicated that the password should be saved, we'll ask //the proto provider factory to store it for us. if (credentials.isPasswordPersistent()) { IrcActivator.getProtocolProviderFactory() .storePassword(getAccountID(), serverPassword); } } // configure client options according to account properties final ClientConfigImpl config = new ClientConfigImpl(); config.setVersion3Allowed(true); config.setContactPresenceTaskEnabled(contactPresenceTask); config.setChannelPresenceTaskEnabled(channelPresenceTask); final Proxy proxy = loadProxy(); config.setProxy(proxy); config.setResolveByProxy(resolveDnsThroughProxy); if (saslEnabled) { final SASLImpl sasl = new ClientConfigImpl.SASLImpl(saslUser, serverPassword, saslRole); config.setSASL(sasl); } // FIXME fix 'replacement' plugins which now (probably) don't use global // proxy configuration when contacting URLs on the internet try { this.ircstack.connect(serverAddress, serverPort, serverPassword, secureConnection, autoNickChange, config); } catch (OperationFailedException e) { // Just rethrow operation failed exception. No need to wrap. throw e; } catch (Exception e) { throw new OperationFailedException(e.getMessage(), OperationFailedException.GENERAL_ERROR, e); } } /** * Get proxy instance based on Jitsi's global proxy configuration. * * @return returns configured proxy instance */ private Proxy loadProxy() throws OperationFailedException { final ConfigurationService configSvc = IrcActivator.getConfigurationService(); if (configSvc == null) { return null; } final String globalProxyType = configSvc.getString(ProxyInfo.CONNECTION_PROXY_TYPE_PROPERTY_NAME); if (globalProxyType == null || (!globalProxyType.equals(ProxyInfo.ProxyType.SOCKS4.name()) &&!globalProxyType.equals(ProxyInfo.ProxyType.SOCKS5.name()))) { // Only SOCKS proxy is supported. The appropriate proxy type is not // configured, so we're done. return null; } final String globalProxyAddress = configSvc .getString(ProxyInfo.CONNECTION_PROXY_ADDRESS_PROPERTY_NAME); final String globalProxyPortStr = configSvc.getString(ProxyInfo.CONNECTION_PROXY_PORT_PROPERTY_NAME); final int globalProxyPort; try { globalProxyPort = Integer.parseInt(globalProxyPortStr); } catch (NumberFormatException e) { throw new OperationFailedException("invalid proxy port", OperationFailedException.INVALID_ACCOUNT_PROPERTIES, e); } return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress( globalProxyAddress, globalProxyPort)); } /** * 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() { if (!isInitialized) { return; } if (LOGGER.isTraceEnabled()) { LOGGER.trace("Killing the Irc Protocol Provider."); } try { synchronized (this.initializationLock) { unregister(); this.ircstack.dispose(); ircstack = null; } } catch (OperationFailedException ex) { // we're shutting down so we need to silence the exception here LOGGER.error("Failed to properly unregister before shutting down. " + getAccountID(), ex); } } /** * Ends the registration of this protocol provider with the current * registration service. * * @throws OperationFailedException with the corresponding code it the * registration fails for some reason (e.g. a networking error or an * implementation problem). */ public void unregister() throws OperationFailedException { if (this.ircstack == null) { return; } this.ircstack.disconnect(); } /** * {@inheritDoc} * * @return returns true in case of secure transport, or false if transport * is not secure */ @Override public boolean isSignalingTransportSecure() { final IrcConnection connection = this.ircstack.getConnection(); return connection != null && 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. */ public TransportProtocol getTransportProtocol() { return TransportProtocol.TCP; } /** * Returns the icon for this protocol. * * @return the icon for this protocol */ public ProtocolIcon getProtocolIcon() { return ircIcon; } /** * Returns the IRC stack implementation. * * @return the IRC stack implementation. */ public IrcStack getIrcStack() { return ircstack; } /** * Returns the current registration state of this protocol provider. * * @return the current registration state of this protocol provider */ protected RegistrationState getCurrentRegistrationState() { return currentRegistrationState; } /** * Sets the current registration state of this protocol provider. * * @param regState the new registration state to set * @param reason reason for the state change */ protected void setCurrentRegistrationState( final RegistrationState regState, final int reason) { final RegistrationState oldState = this.currentRegistrationState; this.currentRegistrationState = regState; fireRegistrationStateChanged( oldState, this.currentRegistrationState, reason, null); } }