/** * $RCSfile$ * $Revision: $ * $Date: $ * * Copyright (C) 2005-2008 Jive Software. All rights reserved. * * 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 org.jivesoftware.smack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.cert.Certificate; import java.net.InetSocketAddress; import javax.xml.XMLConstants; import java.io.StringReader; import java.util.Locale; import org.jivesoftware.openfire.session.LocalClientSession; import org.jivesoftware.openfire.net.VirtualConnection; import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.AuthToken; import org.jivesoftware.openfire.auth.AuthFactory; import org.jivesoftware.openfire.PacketDeliverer; import org.jivesoftware.openfire.nio.OfflinePacketDeliverer; import org.jivesoftware.openfire.*; import org.jivesoftware.openfire.multiplex.UnknownStanzaException; import org.jivesoftware.openfire.net.SASLAuthentication; import org.jivesoftware.openfire.net.SASLAuthentication.Status; import org.jivesoftware.openfire.session.ConnectionSettings; import org.jivesoftware.openfire.session.LocalClientSession; import org.jivesoftware.openfire.streammanagement.StreamManager; import org.jivesoftware.openfire.plugin.ofmeet.jetty.OfMeetLoginService; import org.jivesoftware.openfire.plugin.rest.RestEventSourceServlet; import java.util.*; import java.util.concurrent.*; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.packet.Packet; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.PacketParserUtils; import java.io.*; import java.net.*; import javax.net.ssl.*; import javax.security.auth.callback.*; import org.apache.commons.pool2.impl.GenericObjectPool; import org.xmlpull.mxp1.MXParser; import org.xmlpull.v1.XmlPullParser; import org.xmpp.packet.*; import org.dom4j.*; import org.dom4j.io.XMPPPacketReader; public class XMPPConnection extends Connection { private static Logger Log = LoggerFactory.getLogger( "XMPPConnection" ); String connectionID; private String user; private boolean connected; private boolean authenticated; private boolean wasAuthenticated; private boolean anonymous; private boolean usingTLS; Roster roster; private SSLContext customSslContext; private boolean usingCompression; private LocalClientSession session; OpenfirePacketWriter packetWriter; OpenfirePacketReader packetReader; SmackConnection smackConnection; public XMPPConnection(String serviceName, CallbackHandler callbackHandler) { super(new ConnectionConfiguration(serviceName)); connectionID = null; user = null; connected = false; authenticated = false; wasAuthenticated = false; anonymous = false; usingTLS = false; roster = null; customSslContext = null; config.setCompressionEnabled(false); config.setSASLAuthenticationEnabled(true); config.setDebuggerEnabled(DEBUG_ENABLED); config.setCallbackHandler(callbackHandler); } public XMPPConnection(String serviceName) { super(new ConnectionConfiguration(serviceName)); connectionID = null; user = null; connected = false; authenticated = false; wasAuthenticated = false; anonymous = false; usingTLS = false; roster = null; customSslContext = null; config.setCompressionEnabled(false); config.setSASLAuthenticationEnabled(true); config.setDebuggerEnabled(DEBUG_ENABLED); } public XMPPConnection(ConnectionConfiguration config) { super(config); connectionID = null; user = null; connected = false; authenticated = false; wasAuthenticated = false; anonymous = false; usingTLS = false; roster = null; customSslContext = null; } public XMPPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler) { super(config); connectionID = null; user = null; connected = false; authenticated = false; wasAuthenticated = false; anonymous = false; usingTLS = false; roster = null; customSslContext = null; config.setCallbackHandler(callbackHandler); } public String getConnectionID() { if(!isConnected()) return null; else return connectionID; } public String getUser() { if(!isAuthenticated()) return null; else return user; } public void setCustomSslContext(SSLContext customSslContext) { this.customSslContext = customSslContext; } public synchronized void login(String username, String password, String resource) throws XMPPException { Log.info("XMPPConnection login"); if(!isConnected()) throw new IllegalStateException("Not connected to server."); if(authenticated) throw new IllegalStateException("Already logged in to server."); try { username = username.toLowerCase().trim(); user = username; config.setServiceName(StringUtils.parseServer("openfire")); JID userJid = XMPPServer.getInstance().createJID(username, resource); session = (LocalClientSession) SessionManager.getInstance().getSession(userJid); if (session != null) { session.close(); SessionManager.getInstance().removeSession(session); } AuthToken authToken = null; if (OfMeetLoginService.authTokens.containsKey(username)) { authToken = OfMeetLoginService.authTokens.get(username); } else { try { authToken = AuthFactory.authenticate( username, password ); } catch ( UnauthorizedException e ) { authToken = new AuthToken(resource, true); } } session = SessionManager.getInstance().createClientSession( smackConnection, (Locale) null ); smackConnection.setRouter( new SessionPacketRouter( session ), username ); session.setAuthToken(authToken, resource); Log.warn("login - creating new session (smack) for " + username); authenticated = true; anonymous = false; if(roster == null) roster = new Roster(this); if(config.isRosterLoadedAtLogin()) roster.reload(); packetWriter.sendPacket(new Presence(org.jivesoftware.smack.packet.Presence.Type.available)); config.setLoginInfo(username, password, resource); if(config.isDebuggerEnabled() && debugger != null) debugger.userHasLogged(user); } catch (Exception e) { Log.error("XMPPConnection login error", e); } } public synchronized void loginAnonymously() throws XMPPException { loginAnonymousUser("ofmeet-user-" + System.currentTimeMillis()); } public synchronized void loginAnonymousUser(String userId) throws XMPPException { if (!isConnected()) { throw new IllegalStateException("Not connected to server."); } if (authenticated) { throw new IllegalStateException("Already logged in to server."); } this.user = userId; config.setServiceName(StringUtils.parseServer("openfire")); AuthToken authToken = new AuthToken(this.user, true); session = SessionManager.getInstance().createClientSession( smackConnection, (Locale) null ); smackConnection.setRouter( new SessionPacketRouter( session ), userId ); session.setAuthToken(authToken, this.user); // Set presence to online. packetWriter.sendPacket(new Presence(Presence.Type.available)); // Indicate that we're now authenticated. authenticated = true; anonymous = true; // If debugging is enabled, change the the debug window title to include the // name we are now logged-in as. // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger // will be null if (config.isDebuggerEnabled() && debugger != null) { debugger.userHasLogged(user); } } public Roster getRoster() { // synchronize against login() synchronized(this) { // if connection is authenticated the roster is already set by login() // or a previous call to getRoster() if (!isAuthenticated() || isAnonymous()) { if (roster == null) { roster = new Roster(this); } return roster; } } if (!config.isRosterLoadedAtLogin()) { roster.reload(); } // If this is the first time the user has asked for the roster after calling // login, we want to wait for the server to send back the user's roster. This // behavior shields API users from having to worry about the fact that roster // operations are asynchronous, although they'll still have to listen for // changes to the roster. Note: because of this waiting logic, internal // Smack code should be wary about calling the getRoster method, and may need to // access the roster object directly. if (!roster.rosterInitialized) { try { synchronized (roster) { long waitTime = SmackConfiguration.getPacketReplyTimeout(); long start = System.currentTimeMillis(); while (!roster.rosterInitialized) { if (waitTime <= 0) { break; } roster.wait(waitTime); long now = System.currentTimeMillis(); waitTime -= now - start; start = now; } } } catch (InterruptedException ie) { // Ignore. } } return roster; } public boolean isConnected() { return connected; } public boolean isSecureConnection() { return isUsingTLS(); } public boolean isAuthenticated() { return authenticated; } public boolean isAnonymous() { return anonymous; } protected void shutdown(Presence unavailablePresence) { packetWriter.sendPacket(unavailablePresence); setWasAuthenticated(authenticated); authenticated = false; connected = false; packetReader.shutdown(); packetWriter.shutdown(); } public synchronized void disconnect(Presence unavailablePresence) { shutdown(unavailablePresence); if(roster != null) { roster.cleanup(); roster = null; } wasAuthenticated = false; packetWriter.cleanup(); packetReader.cleanup(); if (session != null) { session.close(); SessionManager.getInstance().removeSession(session); } } public void sendPacket(Packet packet) { try { sendPacket(packet.toXML()); } catch (Exception e) { Log.error("XMPPConnection sendPacket error", e); } } public void sendPacket(String packet) { if(!isConnected()) throw new IllegalStateException("Not connected to server."); if(packet == null) { throw new NullPointerException("Packet is null."); } else { packetWriter.sendPacket(packet); return; } } public void sendRawXmppMessage(String data) { try { Log.info("endRawXmppMessage " + data ); packetWriter.sendRawXmppMessage(data); } catch ( Exception e ) { Log.error( "An error occurred while attempting to route the packet : ", e ); } } /** * @deprecated Method addPacketWriterInterceptor is deprecated */ public void addPacketWriterInterceptor(PacketInterceptor packetInterceptor, PacketFilter packetFilter) { addPacketInterceptor(packetInterceptor, packetFilter); } /** * @deprecated Method removePacketWriterInterceptor is deprecated */ public void removePacketWriterInterceptor(PacketInterceptor packetInterceptor) { removePacketInterceptor(packetInterceptor); } /** * @deprecated Method addPacketWriterListener is deprecated */ public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) { addPacketSendingListener(packetListener, packetFilter); } /** * @deprecated Method removePacketWriterListener is deprecated */ public void removePacketWriterListener(PacketListener packetListener) { removePacketSendingListener(packetListener); } private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException { String host = config.getHost(); int port = config.getPort(); initConnection(host); } private void initConnection(String host) throws XMPPException { boolean isFirstInitialization = packetReader == null || packetWriter == null; if (!isFirstInitialization) { usingCompression = false; } try { if (isFirstInitialization) { packetWriter = new OpenfirePacketWriter(this); packetReader = new OpenfirePacketReader(this); smackConnection = new SmackConnection(host, packetWriter, packetReader); // If debugging is enabled, we should start the thread that will listen for // all packets and then log them. if (config.isDebuggerEnabled()) { addPacketListener(debugger.getReaderListener(), null); if (debugger.getWriterListener() != null) { addPacketSendingListener(debugger.getWriterListener(), null); } } } else { packetWriter.init(); packetReader.init(); } // Start the packet writer. This will open a XMPP stream to the server packetWriter.startup(); // Start the packet reader. The startup() method will block until we // get an opening stream packet back from server. packetReader.startup(); // Make note of the fact that we're now connected. connected = true; // Start keep alive process (after TLS was negotiated - if available) packetWriter.startKeepAliveProcess(); if (isFirstInitialization) { // Notify listeners that a new connection has been established for (ConnectionCreationListener listener : getConnectionCreationListeners()) { listener.connectionCreated(this); } } else if (!wasAuthenticated) { packetReader.notifyReconnection(); } } catch (Exception ex) { // An exception occurred in setting up the connection. Make sure we shut down the if (packetWriter != null) { try { packetWriter.shutdown(); } catch (Throwable ignore) { /* ignore */ } packetWriter = null; } if (packetReader != null) { try { packetReader.shutdown(); } catch (Throwable ignore) { /* ignore */ } packetReader = null; } this.setWasAuthenticated(authenticated); authenticated = false; connected = false; throw ex; // Everything stoppped. Now throw the exception. } } public boolean isUsingTLS() { return usingTLS; } public boolean isUsingCompression() { return usingCompression; } public void connect() throws XMPPException { connectUsingConfiguration(config); if(connected && wasAuthenticated) try { if(isAnonymous()) loginAnonymously(); else login(config.getUsername(), config.getPassword(), config.getResource()); } catch(XMPPException e) { e.printStackTrace(); } } private void setWasAuthenticated(boolean wasAuthenticated) { if(!this.wasAuthenticated) this.wasAuthenticated = wasAuthenticated; } public Socket getSocket() { return null; } public class OpenfirePacketWriter { public XMPPConnection connection; private Thread keepAliveThread; private long lastActive = System.currentTimeMillis(); private boolean done; OpenfirePacketWriter(XMPPConnection connection) { this.connection = connection; init(); } public void init() { done = false; } public void sendRawXmppMessage(String data) { try { Log.info("OpenfirePacketWriter sendRawXmppMessage " + data ); smackConnection.sendRawXmppMessage(data); } catch ( Exception e ) { Log.error( "An error occurred while attempting to route the packet : ", e ); } } public void sendPacket(String data) { try { Log.info("OpenfirePacketWriter sendPacket " + data ); smackConnection.sendSmackXmppMessage(data); } catch ( Exception e ) { Log.error( "An error occurred while attempting to route the packet : ", e ); } } public void sendPacket(Packet packet) { try { String data = packet.toXML(); Log.info("OpenfirePacketWriter sendPacket " + data ); sendPacket(data); connection.firePacketInterceptors(packet); connection.firePacketSendingListeners(packet); } catch ( Exception e ) { Log.error( "An error occurred while attempting to route the packet : ", e ); } } public void startup() { } public void shutdown() { done = true; } void cleanup() { Log.info("OpenfirePacketWriter cleanup"); connection.interceptors.clear(); connection.sendListeners.clear(); } public void startKeepAliveProcess() { Log.info("OpenfirePacketWriter startKeepAliveProcess"); // Schedule a keep-alive task to run if the feature is enabled. will write // out a space character each time it runs to keep the TCP/IP connection open. int keepAliveInterval = SmackConfiguration.getKeepAliveInterval(); if (keepAliveInterval > 0) { KeepAliveTask task = new KeepAliveTask(keepAliveInterval); keepAliveThread = new Thread(task); task.setThread(keepAliveThread); keepAliveThread.setDaemon(true); keepAliveThread.setName("Smack Keep Alive (" + connection.connectionCounterValue + ")"); keepAliveThread.start(); } } private class KeepAliveTask implements Runnable { private int delay; private Thread thread; public KeepAliveTask(int delay) { this.delay = delay; } protected void setThread(Thread thread) { this.thread = thread; } public void run() { try { // Sleep 15 seconds before sending first heartbeat. This will give time to // properly finish TLS negotiation and then start sending heartbeats. Thread.sleep(15000); } catch (InterruptedException ie) { // Do nothing } while (!done && keepAliveThread == thread) { synchronized (smackConnection) { // Send heartbeat if no packet has been sent to the server for a given time if (System.currentTimeMillis() - lastActive >= delay) { try { //if (smackConnection != null) smackConnection.send(" "); } catch (Exception e) { // Do nothing } } } try { // Sleep until we should write the next keep-alive. Thread.sleep(delay); } catch (InterruptedException ie) { // Do nothing } } } } } public class OpenfirePacketReader { public XMPPConnection connection; private ExecutorService listenerExecutor; OpenfirePacketReader(XMPPConnection connection) { this.connection = connection; init(); } public void init() { Log.info("OpenfirePacketReader init"); listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable, "Smack Listener Processor (" + connection.connectionCounterValue + ")"); thread.setDaemon(true); return thread; } }); } public void startup() { Log.info("OpenfirePacketReader startup"); } public void shutdown() { Log.info("OpenfirePacketReader shutdown"); listenerExecutor.shutdown(); } public void cleanup() { Log.info("OpenfirePacketReader cleanup"); connection.interceptors.clear(); connection.sendListeners.clear(); } public void processPacket(Packet packet) { if (packet == null) { return; } Log.debug("OpenfirePacketReader processPacket\n" + packet.toXML()); // Loop through all collectors and notify the appropriate ones. for (PacketCollector collector: connection.getPacketCollectors()) { collector.processPacket(packet); } // Deliver the incoming packet to listeners. listenerExecutor.submit(new ListenerNotification(packet)); } public void notifyReconnection() { Log.info("OpenfirePacketReader notifyReconnection"); for (ConnectionListener listener : connection.getConnectionListeners()) { try { listener.reconnectionSuccessful(); } catch (Exception e) { } } } private class ListenerNotification implements Runnable { private Packet packet; public ListenerNotification(Packet packet) { this.packet = packet; } public void run() { for (ListenerWrapper listenerWrapper : connection.recvListeners.values()) { listenerWrapper.notifyListener(packet); } } } } }