diff options
author | Tarun Gupta <tarun1995gupta@gmail.com> | 2015-07-26 07:47:40 (GMT) |
---|---|---|
committer | Tarun Gupta <tarun1995gupta@gmail.com> | 2015-08-17 13:21:01 (GMT) |
commit | f56f245c1f7e768caf356a0e7b57f428cf8cc6da (patch) | |
tree | f0935ab00c73e86163abe72fc9c20098121fff4f /src | |
parent | 2533374644704040ca67aba4e1240a9d6ea450c8 (diff) | |
download | stroke-f56f245c1f7e768caf356a0e7b57f428cf8cc6da.zip stroke-f56f245c1f7e768caf356a0e7b57f428cf8cc6da.tar.bz2 |
Update Client.
Updates all bits of Client to be in parity with Swiften.
Some part of code which depends on Network is commented.
License:
This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.
Test-Information:
Tests added for: ClientBlockListManager, ClientSession and NickResolver.
All tests pass.
Change-Id: I5b9b0e5ae5b2df58202e2349ba24e4544d9a4614
Diffstat (limited to 'src')
20 files changed, 1098 insertions, 130 deletions
diff --git a/src/com/isode/stroke/client/BlockList.java b/src/com/isode/stroke/client/BlockList.java new file mode 100644 index 0000000..bf72f45 --- /dev/null +++ b/src/com/isode/stroke/client/BlockList.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2011-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.client; + +import com.isode.stroke.signals.Signal1; +import com.isode.stroke.signals.Signal; +import com.isode.stroke.jid.JID; +import java.util.Vector; + +public abstract class BlockList { + + public enum State { + Init, + Requesting, + Available, + Error + }; + + public final Signal onStateChanged = new Signal(); + public final Signal1<JID> onItemAdded = new Signal1<JID>(); + public final Signal1<JID> onItemRemoved = new Signal1<JID>(); + + public abstract State getState(); + + public abstract Vector<JID> getItems(); + + public boolean isBlocked(final JID jid) { + final Vector<JID> items = getItems(); + return (items.contains(jid.toBare()) || items.contains(new JID(jid.getDomain())) || items.contains(jid)); + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/client/BlockListImpl.java b/src/com/isode/stroke/client/BlockListImpl.java new file mode 100644 index 0000000..a85f86c --- /dev/null +++ b/src/com/isode/stroke/client/BlockListImpl.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2011 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.client; + +import com.isode.stroke.client.BlockList; +import com.isode.stroke.jid.JID; +import java.util.Vector; + +public class BlockListImpl extends BlockList { + + private State state; + private Vector<JID> items = new Vector<JID>(); + + public BlockListImpl() { + this.state = State.Init; + } + + public State getState() { + return state; + } + + public void setState(State state) { + if (!(this.state.equals(state))) { + this.state = state; + onStateChanged.emit(); + } + } + + public Vector<JID> getItems() { + return items; + } + + public void setItems(final Vector<JID> items) { + for (final JID jid : this.items) { + if(items.contains(jid)) { + onItemRemoved.emit(jid); + } + } + + for (final JID jid : items) { + if(this.items.contains(jid)) { + onItemAdded.emit(jid); + } + } + this.items = items; + } + + public void addItem(final JID item) { + if(!(items.contains(item))) { + items.add(item); + onItemAdded.emit(item); + } + } + + public void removeItem(final JID item) { + int oldSize = items.size(); + while(items.contains(item)) { + items.remove(item); + } + if (items.size() != oldSize) { + onItemRemoved.emit(item); + } + } + + public void addItems(final Vector<JID> items) { + Vector<JID> itemsToAdd = new Vector<JID>(items); //Have to do this to avoid ConcurrentModificationException. + for (final JID item : itemsToAdd) { + addItem(item); + } + } + + public void removeItems(final Vector<JID> items) { + Vector<JID> itemsToRemove = new Vector<JID>(items); //Have to do this to avoid ConcurrentModificationException. + for (final JID item : itemsToRemove) { + removeItem(item); + } + } + + public void removeAllItems() { + removeItems(items); + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/client/Client.java b/src/com/isode/stroke/client/Client.java index 1d137c5..464e062 100644 --- a/src/com/isode/stroke/client/Client.java +++ b/src/com/isode/stroke/client/Client.java @@ -29,6 +29,7 @@ import com.isode.stroke.roster.XMPPRosterImpl; import com.isode.stroke.signals.Signal1; import com.isode.stroke.vcards.VCardManager; import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.tls.BlindCertificateTrustChecker; /** * Provides the core functionality for writing XMPP client software. @@ -44,7 +45,7 @@ public class Client extends CoreClient { private final DirectedPresenceSender directedPresenceSender; //NOPMD, this is not better as a local variable private final StanzaChannelPresenceSender stanzaChannelPresenceSender; //NOPMD, this is not better as a local variable private final SoftwareVersionResponder softwareVersionResponder; - private final PubSubManager pubSubManager; + private final PubSubManager pubsubManager; private final XMPPRosterImpl roster; private final XMPPRosterController rosterController; private final PresenceOracle presenceOracle; @@ -58,8 +59,13 @@ public class Client extends CoreClient { private final SubscriptionManager subscriptionManager; private final ClientDiscoManager discoManager; private final AvatarManager avatarManager; + //private final JingleSessionManager jingleSessionManager; + //private final FileTransferManager fileTransferManager; + private final BlindCertificateTrustChecker blindCertificateTrustChecker; + //private final WhiteboardSessionManager whiteboardSessionManager; + private final ClientBlockListManager blockListManager; - final Signal1<Presence> onPresenceChange = new Signal1<Presence>(); + public final Signal1<Presence> onPresenceChange = new Signal1<Presence>(); /** * Constructor. @@ -109,9 +115,45 @@ public class Client extends CoreClient { nickManager = new NickManagerImpl(jid.toBare(), vcardManager); nickResolver = new NickResolver(jid.toBare(), roster, vcardManager, mucRegistry); - pubSubManager = new PubSubManagerImpl(getStanzaChannel(), getIQRouter()); + blindCertificateTrustChecker = new BlindCertificateTrustChecker(); + + //TO PORT + //jingleSessionManager = new JingleSessionManager(getIQRouter()); + blockListManager = new ClientBlockListManager(getIQRouter()); + + /*whiteboardSessionManager = NULL; + #ifdef SWIFT_EXPERIMENTAL_WB + whiteboardSessionManager = new WhiteboardSessionManager(getIQRouter(), getStanzaChannel(), presenceOracle, getEntityCapsProvider()); + #endif*/ + + pubsubManager = new PubSubManagerImpl(getStanzaChannel(), getIQRouter()); + + /*#ifdef SWIFT_EXPERIMENTAL_FT + fileTransferManager = new FileTransferManagerImpl( + getJID(), + jingleSessionManager, + getIQRouter(), + getEntityCapsProvider(), + presenceOracle, + getNetworkFactories()->getConnectionFactory(), + getNetworkFactories()->getConnectionServerFactory(), + getNetworkFactories()->getTimerFactory(), + getNetworkFactories()->getDomainNameResolver(), + getNetworkFactories()->getNetworkEnvironment(), + getNetworkFactories()->getNATTraverser(), + getNetworkFactories()->getCryptoProvider()); + #else + fileTransferManager = new DummyFileTransferManager(); + #endif*/ } - + + /** + * Constructs a client for the given JID with the given password. + * + * \param storages The interfaces for storing cache information etc. If + * this is NULL, + * all data will be stored in memory (and be lost on shutdown) + */ public Client(final JID jid, final SafeByteArray password, final NetworkFactories networkFactories) { this(jid, password, networkFactories, null); } @@ -137,13 +179,32 @@ public class Client extends CoreClient { * @return PubSub manager, not null */ public PubSubManager getPubSubManager() { - return pubSubManager; + return pubsubManager; } - + + /** + * Returns a representation of the roster. + * + * The roster is initially empty. To populate it, call requestRoster(), + * which will request the roster from the server. When the roster has + * been requested, it will also be kept up to date when it is updated on + * the server side. + * + * This pointer remains the same across the lifetime of Client. All + * changes to the roster (e.g. after the initial roster request, or after + * subsequent roster updates) are notified through the XMPPRoster's + * signals. + * + * \see requestRoster() + */ public XMPPRoster getRoster() { return roster; } - + + public void setSoftwareVersion(final String name, final String version) { + setSoftwareVersion(name, version, ""); + } + /** * Sets the software version of the client. * @@ -153,7 +214,11 @@ public class Client extends CoreClient { softwareVersionResponder.setVersion(name, version, os); } - + /** + * Requests the roster from the server. + * + * \see getRoster() + */ public void requestRoster() { // FIXME: We should set this once when the session is finished, but there // is currently no callback for this @@ -190,7 +255,37 @@ public class Client extends CoreClient { public ClientDiscoManager getDiscoManager() { return discoManager; } - + + public ClientBlockListManager getClientBlockListManager() { + return blockListManager; + } + + /** + * Returns a FileTransferManager for the client. This is only available after the onConnected + * signal has been fired. + * + * WARNING: File transfer will only work if Swiften is built in 'experimental' mode. + */ + //TO PORT + /*public FileTransferManager getFileTransferManager() { + return fileTransferManager; + }*/ + + /** + * Configures the client to always trust a non-validating + * TLS certificate from the server. + * This is equivalent to setting a BlindCertificateTrustChecker + * using setCertificateTrustChecker(). + */ + public void setAlwaysTrustCertificates() { + setCertificateTrustChecker(blindCertificateTrustChecker); + } + + //TO PORT + /*public WhiteboardSessionManager getWhiteboardSessionManager() { + return whiteboardSessionManager; + }*/ + public VCardManager getVCardManager() { return vcardManager; } @@ -205,7 +300,11 @@ public class Client extends CoreClient { } return memoryStorages; } - + + protected void handleConnected() { + discoManager.handleConnected(); + } + public PresenceSender getPresenceSender() { return discoManager.getPresenceSender(); } diff --git a/src/com/isode/stroke/client/ClientBlockListManager.java b/src/com/isode/stroke/client/ClientBlockListManager.java new file mode 100644 index 0000000..b370f63 --- /dev/null +++ b/src/com/isode/stroke/client/ClientBlockListManager.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2011-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.client; + +import com.isode.stroke.elements.BlockPayload; +import com.isode.stroke.elements.BlockListPayload; +import com.isode.stroke.elements.UnblockPayload; +import com.isode.stroke.elements.DiscoInfo; +import com.isode.stroke.elements.ErrorPayload; +import com.isode.stroke.elements.IQ; +import com.isode.stroke.signals.Slot2; +import com.isode.stroke.queries.SetResponder; +import com.isode.stroke.queries.GenericRequest; +import com.isode.stroke.client.BlockList; +import com.isode.stroke.client.BlockListImpl; +import com.isode.stroke.queries.IQRouter; +import com.isode.stroke.jid.JID; +import java.util.Vector; + +public class ClientBlockListManager { + + private IQRouter iqRouter; + private SetResponder<BlockPayload> blockResponder; + private SetResponder<UnblockPayload> unblockResponder; + private BlockListImpl blockList; + + class BlockResponder extends SetResponder<BlockPayload> { + + public BlockResponder(BlockListImpl blockList, IQRouter iqRouter) { + super(new BlockPayload(), iqRouter); + this.blockList = blockList; + } + + public boolean handleSetRequest(final JID from, final JID to, final String id, BlockPayload payload) { + if (getIQRouter().isAccountJID(from)) { + if (payload != null) { + blockList.addItems(payload.getItems()); + } + sendResponse(from, id, null); + } + else { + sendError(from, id, ErrorPayload.Condition.NotAuthorized, ErrorPayload.Type.Cancel); + } + return true; + } + + private BlockListImpl blockList; + }; + + class UnblockResponder extends SetResponder<UnblockPayload> { + + public UnblockResponder(BlockListImpl blockList, IQRouter iqRouter) { + super(new UnblockPayload(), iqRouter); + this.blockList = blockList; + } + + public boolean handleSetRequest(final JID from, final JID to, final String id, UnblockPayload payload) { + if (getIQRouter().isAccountJID(from)) { + if (payload != null) { + if (payload.getItems().isEmpty()) { + blockList.removeAllItems(); + } + else { + blockList.removeItems(payload.getItems()); + } + } + sendResponse(from, id, null); + } + else { + sendError(from, id, ErrorPayload.Condition.NotAuthorized, ErrorPayload.Type.Cancel); + } + return true; + } + + private BlockListImpl blockList; + }; + + public ClientBlockListManager(IQRouter iqRouter) { + this.iqRouter = iqRouter; + } + + protected void finalize() throws Throwable { + try { + if (blockList != null && BlockList.State.Available.equals(blockList.getState())) { + unblockResponder.stop(); + blockResponder.stop(); + } + } + finally { + super.finalize(); + } + } + + /** + * Returns the blocklist. + */ + public BlockList getBlockList() { + if (blockList == null) { + blockList = new BlockListImpl(); + blockList.setState(BlockList.State.Init); + } + return blockList; + } + + /** + * Get the blocklist from the server. + */ + public BlockList requestBlockList() { + if (blockList == null) { + blockList = new BlockListImpl(); + } + blockList.setState(BlockList.State.Requesting); + GenericRequest<BlockListPayload> getRequest = new GenericRequest<BlockListPayload>(IQ.Type.Get, new JID(), new BlockListPayload(), iqRouter); + getRequest.onResponse.connect(new Slot2<BlockListPayload, ErrorPayload>() { + @Override + public void call(BlockListPayload p, ErrorPayload e) { + handleBlockListReceived(p, e); + } + }); + getRequest.send(); + return blockList; + } + + public GenericRequest<BlockPayload> createBlockJIDRequest(final JID jid) { + Vector<JID> vec = new Vector<JID>(); + vec.add(jid); + return createBlockJIDsRequest(vec); + } + + public GenericRequest<BlockPayload> createBlockJIDsRequest(final Vector<JID> jids) { + BlockPayload payload = new BlockPayload(jids); + return new GenericRequest<BlockPayload>(IQ.Type.Set, new JID(), payload, iqRouter); + } + + public GenericRequest<UnblockPayload> createUnblockJIDRequest(final JID jid) { + Vector<JID> vec = new Vector<JID>(); + vec.add(jid); + return createUnblockJIDsRequest(vec); + } + + public GenericRequest<UnblockPayload> createUnblockJIDsRequest(final Vector<JID> jids) { + UnblockPayload payload = new UnblockPayload(jids); + return new GenericRequest<UnblockPayload>(IQ.Type.Set, new JID(), payload, iqRouter); + } + + public GenericRequest<UnblockPayload> createUnblockAllRequest() { + return createUnblockJIDsRequest(new Vector<JID>()); + } + + private void handleBlockListReceived(BlockListPayload payload, ErrorPayload error) { + if (error != null || payload == null) { + blockList.setState(BlockList.State.Error); + } + else { + blockList.setItems(payload.getItems()); + blockList.setState(BlockList.State.Available); + if (blockResponder == null) { + blockResponder = new BlockResponder(blockList, iqRouter); + blockResponder.start(); + } + if (unblockResponder == null) { + unblockResponder = new UnblockResponder(blockList, iqRouter); + unblockResponder.start(); + } + } + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/client/ClientError.java b/src/com/isode/stroke/client/ClientError.java index de2db49..cfcf12f 100644 --- a/src/com/isode/stroke/client/ClientError.java +++ b/src/com/isode/stroke/client/ClientError.java @@ -30,9 +30,14 @@ public class ClientError { UnexpectedElementError, ResourceBindError, SessionStartError, + StreamError, TLSError, ClientCertificateLoadError, ClientCertificateError, + + // Certifate on smartcard was removed + CertificateCardRemoved, + // Certificate verification errors UnknownCertificateError, CertificateExpiredError, @@ -45,9 +50,15 @@ public class ClientError { InvalidCertificateSignatureError, InvalidCAError, InvalidServerIdentityError, + RevokedError, + RevocationCheckFailedError }; - ClientError(final Type type) { + public ClientError() { + type_ = Type.UnknownError; + } + + public ClientError(final Type type) { type_ = type; } diff --git a/src/com/isode/stroke/client/ClientOptions.java b/src/com/isode/stroke/client/ClientOptions.java index b410094..c2d9e3f 100644 --- a/src/com/isode/stroke/client/ClientOptions.java +++ b/src/com/isode/stroke/client/ClientOptions.java @@ -10,6 +10,8 @@ package com.isode.stroke.client; import com.isode.stroke.tls.TLSOptions; +import com.isode.stroke.base.URL; +import com.isode.stroke.base.SafeByteArray; /** * Options for a client connection @@ -40,7 +42,17 @@ public class ClientOptions { * Default: false */ public boolean useStreamResumption; - + + /** + * Forget the password once it's used. + * This makes the Client useless after the first login attempt. + * + * FIXME: This is a temporary workaround. + * + * Default: false + */ + public boolean forgetPassword; + /** * Use XEP-0198 acks in the stream when available. * Default: true @@ -48,6 +60,12 @@ public class ClientOptions { public boolean useAcks; /** + * Use Single Sign On. + * Default: false + */ + public boolean singleSignOn; + + /** * The hostname to connect to. * Leave this empty for standard XMPP connection, based on the JID domain. */ @@ -60,6 +78,48 @@ public class ClientOptions { */ public int manualPort; + /** + * The type of proxy to use for connecting to the XMPP + * server. + */ + public ProxyType proxyType; + + /** + * Override the system-configured proxy hostname. + */ + public String manualProxyHostname; + + /** + * Override the system-configured proxy port. + */ + public int manualProxyPort; + + /** + * If non-empty, use BOSH instead of direct TCP, with the given URL. + * Default: empty (no BOSH) + */ + public URL boshURL = new URL(); + + /** + * If non-empty, BOSH connections will try to connect over this HTTP CONNECT + * proxy instead of directly. + * Default: empty (no proxy) + */ + public URL boshHTTPConnectProxyURL = new URL(); + + /** + * If this and matching Password are non-empty, BOSH connections over + * HTTP CONNECT proxies will use these credentials for proxy access. + * Default: empty (no authentication needed by the proxy) + */ + public SafeByteArray boshHTTPConnectProxyAuthID; + public SafeByteArray boshHTTPConnectProxyAuthPassword; + + /** + * This can be initialized with a custom HTTPTrafficFilter, which allows HTTP CONNECT + * proxy initialization to be customized. + */ + //public HTTPTrafficFilter httpTrafficFilter; //TOPORT NETWORK /** * Options passed to the TLS stack @@ -72,14 +132,28 @@ public class ClientOptions { RequireTLS } + public enum ProxyType { + NoProxy, + SystemConfiguredProxy, + SOCKS5Proxy, + HTTPConnectProxy + }; + public ClientOptions() { useStreamCompression = true; useTLS = UseTLS.UseTLSWhenAvailable; - useStreamResumption = false; allowPLAINWithoutTLS = false; + useStreamResumption = false; + forgetPassword = false; useAcks = true; + singleSignOn = false; manualHostname = ""; manualPort = -1; + proxyType = ProxyType.SystemConfiguredProxy; + manualProxyHostname = ""; + manualProxyPort = -1; + boshHTTPConnectProxyAuthID = new SafeByteArray(""); + boshHTTPConnectProxyAuthPassword = new SafeByteArray(""); } @Override diff --git a/src/com/isode/stroke/client/ClientSession.java b/src/com/isode/stroke/client/ClientSession.java index b8ec4a9..42d82ca 100644 --- a/src/com/isode/stroke/client/ClientSession.java +++ b/src/com/isode/stroke/client/ClientSession.java @@ -38,6 +38,8 @@ import com.isode.stroke.jid.JID; import com.isode.stroke.sasl.ClientAuthenticator; import com.isode.stroke.sasl.PLAINClientAuthenticator; import com.isode.stroke.sasl.SCRAMSHA1ClientAuthenticator; +import com.isode.stroke.sasl.DIGESTMD5ClientAuthenticator; +import com.isode.stroke.sasl.EXTERNALClientAuthenticator; import com.isode.stroke.session.SessionStream; import com.isode.stroke.signals.Signal; import com.isode.stroke.signals.Signal1; @@ -54,7 +56,7 @@ import com.isode.stroke.idn.IDNConverter; import com.isode.stroke.idn.ICUConverter; import com.isode.stroke.crypto.CryptoProvider; import com.isode.stroke.crypto.JavaCryptoProvider; - +import java.util.logging.Logger; import java.util.List; import java.util.UUID; @@ -65,6 +67,7 @@ public class ClientSession { public final Signal1<Stanza> onStanzaReceived = new Signal1<Stanza>(); public final Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>(); + private Logger logger_ = Logger.getLogger(this.getClass().getName()); private SignalConnection streamElementReceivedConnection; private SignalConnection streamStreamStartReceivedConnection; private SignalConnection streamClosedConnection; @@ -88,8 +91,10 @@ public class ClientSession { private StanzaAckResponder stanzaAckResponder_; private com.isode.stroke.base.Error error_; private CertificateTrustChecker certificateTrustChecker; - private IDNConverter idnConverter = new ICUConverter(); //TOPORT: Accomodated later in Constructor. - private CryptoProvider crypto = new JavaCryptoProvider(); //TOPORT: Accomodated later in Constructor. + private IDNConverter idnConverter; + private CryptoProvider crypto; + private boolean singleSignOn; + private int authenticationPort; public enum State { @@ -126,9 +131,6 @@ public class ClientSession { }; public Error(final Type type) { - if (type == null) { - throw new IllegalStateException(); - } this.type = type; } }; @@ -139,10 +141,12 @@ public class ClientSession { RequireTLS } - private ClientSession(final JID jid, final SessionStream stream) { + private ClientSession(final JID jid, final SessionStream stream, IDNConverter idnConverter, CryptoProvider crypto) { localJID = jid; state = State.Initial; this.stream = stream; + this.idnConverter = idnConverter; + this.crypto = crypto; allowPLAINOverNonTLS = false; useStreamCompression = true; useTLS = UseTLS.UseTLSWhenAvailable; @@ -150,11 +154,15 @@ public class ClientSession { needSessionStart = false; needResourceBind = false; needAcking = false; + rosterVersioningSupported = false; authenticator = null; + certificateTrustChecker = null; + singleSignOn = false; + authenticationPort = -1; } - public static ClientSession create(final JID jid, final SessionStream stream) { - return new ClientSession(jid, stream); + public static ClientSession create(final JID jid, final SessionStream stream, IDNConverter idnConverter, CryptoProvider crypto) { + return new ClientSession(jid, stream, idnConverter, crypto); } public State getState() { @@ -201,6 +209,18 @@ public class ClientSession { certificateTrustChecker = checker; } + public void setSingleSignOn(boolean b) { + singleSignOn = b; + } + + /** + * Sets the port number used in Kerberos authentication + * Does not affect network connectivity. + */ + public void setAuthenticationPort(int i) { + authenticationPort = i; + } + public void start() { streamStreamStartReceivedConnection = stream.onStreamStartReceived.connect(new Slot1<ProtocolHeader>(){ public void call(final ProtocolHeader p1) { @@ -308,13 +328,13 @@ public class ClientSession { if (ack.isValid()) { stanzaAckRequester_.handleAckReceived(ack.getHandledStanzasCount()); } - //else { - //logger_.warning("Got invalid ack from server"); /*FIXME: Do we want logging here? - //} + else { + logger_.warning("Got invalid ack from server"); + } + } + else { + logger_.warning("Ignoring ack"); } - //else { - //logger_.warning("Ignoring ack"); /*FIXME: Do we want logging here?*/ - //} } else if (element instanceof StreamError) { finishSession(Error.Type.StreamError); @@ -341,7 +361,7 @@ public class ClientSession { else if (UseTLS.RequireTLS.equals(useTLS) && !stream.isTLSEncrypted()) { finishSession(Error.Type.NoSupportedAuthMechanismsError); } - else if (useStreamCompression && streamFeatures.hasCompressionMethod("zlib")) { + else if (useStreamCompression && stream.supportsZLibCompression() && streamFeatures.hasCompressionMethod("zlib")) { state = State.Compressing; stream.writeElement(new CompressRequest("zlib")); } @@ -349,20 +369,27 @@ public class ClientSession { if (stream.hasTLSCertificate()) { if (streamFeatures.hasAuthenticationMechanism("EXTERNAL")) { state = State.Authenticating; - stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray())); + stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray(""))); } else { finishSession(Error.Type.TLSClientCertificateError); } } else if (streamFeatures.hasAuthenticationMechanism("EXTERNAL")) { + authenticator = new EXTERNALClientAuthenticator(); state = State.Authenticating; - stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray())); + stream.writeElement(new AuthRequest("EXTERNAL",new SafeByteArray(""))); } else if (streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1") || streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS")) { - final SCRAMSHA1ClientAuthenticator scramAuthenticator = new SCRAMSHA1ClientAuthenticator(UUID.randomUUID().toString(), streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS"), idnConverter, crypto); + ByteArray finishMessage = new ByteArray(); + boolean plus = streamFeatures.hasAuthenticationMechanism("SCRAM-SHA-1-PLUS"); if (stream.isTLSEncrypted()) { - scramAuthenticator.setTLSChannelBindingData(stream.getTLSFinishMessage()); + finishMessage = stream.getTLSFinishMessage(); + plus &= !(finishMessage.isEmpty()); + } + final SCRAMSHA1ClientAuthenticator scramAuthenticator = new SCRAMSHA1ClientAuthenticator(UUID.randomUUID().toString(), plus, idnConverter, crypto); + if (!(finishMessage.isEmpty())) { + scramAuthenticator.setTLSChannelBindingData(finishMessage); } authenticator = scramAuthenticator; state = State.WaitingForCredentials; @@ -373,13 +400,12 @@ public class ClientSession { state = State.WaitingForCredentials; onNeedCredentials.emit(); } -// //FIXME: Port -// else if (streamFeatures.hasAuthenticationMechanism("DIGEST-MD5")) { -// // FIXME: Host should probably be the actual host -// authenticator = new DIGESTMD5ClientAuthenticator(localJID.getDomain(), UUID.randomUUID()); -// state = State.WaitingForCredentials; -// onNeedCredentials.emit(); -// } + else if (streamFeatures.hasAuthenticationMechanism("DIGEST-MD5") && crypto.isMD5AllowedForCrypto()) { + // FIXME: Host should probably be the actual host + authenticator = new DIGESTMD5ClientAuthenticator(localJID.getDomain(), UUID.randomUUID().toString(), crypto); + state = State.WaitingForCredentials; + onNeedCredentials.emit(); + } else { finishSession(Error.Type.NoSupportedAuthMechanismsError); } @@ -400,7 +426,9 @@ public class ClientSession { } } else if (element instanceof Compressed) { - checkState(State.Compressing); + if(!checkState(State.Compressing)) { + return; + } state = State.WaitingForStreamStart; stream.addZLibCompression(); stream.resetXMPPParser(); @@ -439,7 +467,9 @@ public class ClientSession { } else if (element instanceof AuthChallenge) { final AuthChallenge challenge = (AuthChallenge) element; - checkState(State.Authenticating); + if(!checkState(State.Authenticating)) { + return; + } assert authenticator != null; if (authenticator.setChallenge(challenge.getValue())) { stream.writeElement(new AuthResponse(authenticator.getResponse())); @@ -450,8 +480,11 @@ public class ClientSession { } else if (element instanceof AuthSuccess) { final AuthSuccess authSuccess = (AuthSuccess)element; - checkState(State.Authenticating); - if (authenticator != null && !authenticator.setChallenge(authSuccess.getValue())) { + if(!checkState(State.Authenticating)) { + return; + } + assert(authenticator != null); + if (!authenticator.setChallenge(authSuccess.getValue())) { finishSession(Error.Type.ServerVerificationFailedError); } else { @@ -462,7 +495,6 @@ public class ClientSession { } } else if (element instanceof AuthFailure) { - authenticator = null; finishSession(Error.Type.AuthenticationFailedError); } else if (element instanceof TLSProceed) { @@ -518,6 +550,8 @@ public class ClientSession { if (!checkState(State.WaitingForCredentials)) { throw new IllegalStateException("Asking for credentials when we shouldn't be asked."); } + //assert(WaitingForCredentials); + //assert(authenticator); state = State.Authenticating; authenticator.setCredentials(localJID.getNode(), password); stream.writeElement(new AuthRequest(authenticator.getName(), authenticator.getResponse())); @@ -536,7 +570,7 @@ public class ClientSession { } else { final ServerIdentityVerifier identityVerifier = new ServerIdentityVerifier(localJID, idnConverter); - if (identityVerifier.certificateVerifies(peerCertificate)) { + if (!certificateChain.isEmpty() && identityVerifier.certificateVerifies(peerCertificate)) { continueAfterTLSEncrypted(); } else { @@ -592,20 +626,23 @@ public class ClientSession { } private void finishSession(final Error.Type error) { - Error localError = null; - if (error != null) { - localError = new Error(error); - } - finishSession(localError); + finishSession(new ClientSession.Error(error)); } private void finishSession(final com.isode.stroke.base.Error error) { state = State.Finishing; - error_ = error; + if(error_ == null) { + error_ = error; + } else { + logger_.warning("Session finished twice"); + } assert stream.isOpen(); if (stanzaAckResponder_ != null) { stanzaAckResponder_.handleAckRequestReceived(); } + if (authenticator != null) { + authenticator = null; + } stream.writeFooter(); stream.close(); } diff --git a/src/com/isode/stroke/client/ClientSessionStanzaChannel.java b/src/com/isode/stroke/client/ClientSessionStanzaChannel.java index 0aa024f..e8275a0 100644 --- a/src/com/isode/stroke/client/ClientSessionStanzaChannel.java +++ b/src/com/isode/stroke/client/ClientSessionStanzaChannel.java @@ -9,6 +9,7 @@ package com.isode.stroke.client; import com.isode.stroke.base.Error; +import com.isode.stroke.base.IDGenerator; import com.isode.stroke.elements.IQ; import com.isode.stroke.elements.Message; import com.isode.stroke.elements.Presence; @@ -26,7 +27,7 @@ import java.util.logging.Logger; * StanzaChannel implementation around a ClientSession. */ public class ClientSessionStanzaChannel extends StanzaChannel { - private final IDGenerator idGenerator = new IDGenerator(); + private IDGenerator idGenerator = new IDGenerator(); private ClientSession session; private static final Logger logger_ = Logger.getLogger(ClientSessionStanzaChannel.class.getName()); private SignalConnection sessionInitializedConnection; @@ -34,6 +35,21 @@ public class ClientSessionStanzaChannel extends StanzaChannel { private SignalConnection sessionStanzaReceivedConnection; private SignalConnection sessionStanzaAckedConnection; + protected void finalize() throws Throwable { + try { + if(session != null) { + sessionFinishedConnection.disconnect(); + sessionStanzaReceivedConnection.disconnect(); + sessionStanzaAckedConnection.disconnect(); + sessionInitializedConnection.disconnect(); + session = null; + } + } + finally { + super.finalize(); + } + } + public void setSession(final ClientSession session) { assert this.session == null; this.session = session; diff --git a/src/com/isode/stroke/client/ClientXMLTracer.java b/src/com/isode/stroke/client/ClientXMLTracer.java new file mode 100644 index 0000000..e3a65a6 --- /dev/null +++ b/src/com/isode/stroke/client/ClientXMLTracer.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2014 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.client; + +import com.isode.stroke.client.CoreClient; +import com.isode.stroke.client.XMLBeautifier; +import com.isode.stroke.base.SafeByteArray; +import com.isode.stroke.signals.SignalConnection; +import com.isode.stroke.signals.Slot1; + +public class ClientXMLTracer { + + private XMLBeautifier beautifier; + private boolean bosh; + private SignalConnection onDataReadConnection; + private SignalConnection onDataWrittenConnection; + + public ClientXMLTracer(CoreClient client) { + this(client, false); + } + + public ClientXMLTracer(CoreClient client, boolean bosh) { + this.bosh = bosh; + beautifier = new XMLBeautifier(true, true); + onDataReadConnection = client.onDataRead.connect(new Slot1<SafeByteArray>() { + @Override + public void call(SafeByteArray s) { + printData('<', s); + } + }); + onDataWrittenConnection = client.onDataWritten.connect(new Slot1<SafeByteArray>() { + @Override + public void call(SafeByteArray s) { + printData('>', s); + } + }); + } + + private void printData(char direction, final SafeByteArray data) { + printLine(direction); + if (bosh) { + String line = data.toString(); + // Disabled because it swallows bits of XML (namespaces, if I recall) + // size_t endOfHTTP = line.find("\r\n\r\n"); + // if (false && endOfHTTP != std::string::npos) { + // std::cerr << line.substr(0, endOfHTTP) << std::endl << beautifier->beautify(line.substr(endOfHTTP)) << std::endl; + // } + // else { + System.err.println(line); + // } + } + else { + System.err.println(beautifier.beautify(data.toString())); + } + } + + private void printLine(char c) { + for (int i = 0; i < 80; ++i) { + System.err.println(c); + } + System.err.println(); + } +} diff --git a/src/com/isode/stroke/client/CoreClient.java b/src/com/isode/stroke/client/CoreClient.java index 74ba031..2fc10ae 100644 --- a/src/com/isode/stroke/client/CoreClient.java +++ b/src/com/isode/stroke/client/CoreClient.java @@ -11,7 +11,10 @@ import com.isode.stroke.elements.Presence; import com.isode.stroke.elements.Stanza; import com.isode.stroke.elements.StreamType; import com.isode.stroke.jid.JID; +import com.isode.stroke.crypto.JavaCryptoProvider; +import com.isode.stroke.idn.ICUConverter; import com.isode.stroke.network.Connection; +import com.isode.stroke.network.ConnectionFactory; import com.isode.stroke.network.Connector; import com.isode.stroke.network.DomainNameResolveError; import com.isode.stroke.network.NetworkFactories; @@ -30,6 +33,9 @@ import com.isode.stroke.tls.CertificateTrustChecker; import com.isode.stroke.tls.CertificateVerificationError; import com.isode.stroke.tls.CertificateWithKey; import com.isode.stroke.tls.TLSOptions; +import com.isode.stroke.tls.TLSError; +import java.util.logging.Logger; +import java.util.Vector; /** * The central class for communicating with an XMPP server. @@ -85,7 +91,6 @@ public class CoreClient { private ClientSessionStanzaChannel stanzaChannel_; private IQRouter iqRouter_; private Connector connector_; - //private ConnectionFactory connectionFactory_; private FullPayloadParserFactoryCollection payloadParserFactories_ = new FullPayloadParserFactoryCollection(); private FullPayloadSerializerCollection payloadSerializers_ = new FullPayloadSerializerCollection(); private Connection connection_; @@ -101,6 +106,12 @@ public class CoreClient { private SignalConnection sessionFinishedConnection_; private SignalConnection sessionNeedCredentialsConnection_; private SignalConnection connectorConnectFinishedConnection_; + private SignalConnection onMessageReceivedConnection; + private SignalConnection onPresenceReceivedConnection; + private SignalConnection onStanzaAckedConnection; + private SignalConnection onAvailableChangedConnection; + private Vector<ConnectionFactory> proxyConnectionFactories = new Vector<ConnectionFactory>(); + private Logger logger_ = Logger.getLogger(this.getClass().getName()); /** * Constructor. @@ -125,25 +136,25 @@ public class CoreClient { this.networkFactories = networkFactories; this.certificateTrustChecker = null; stanzaChannel_ = new ClientSessionStanzaChannel(); - stanzaChannel_.onMessageReceived.connect(new Slot1<Message>() { + onMessageReceivedConnection = stanzaChannel_.onMessageReceived.connect(new Slot1<Message>() { public void call(Message p1) { - onMessageReceived.emit(p1); + handleMessageReceived(p1); } }); - stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() { + onPresenceReceivedConnection = stanzaChannel_.onPresenceReceived.connect(new Slot1<Presence>() { public void call(Presence p1) { - onPresenceReceived.emit(p1); + handlePresenceReceived(p1); } }); - stanzaChannel_.onStanzaAcked.connect(new Slot1<Stanza>() { + onStanzaAckedConnection = stanzaChannel_.onStanzaAcked.connect(new Slot1<Stanza>() { public void call(Stanza p1) { - onStanzaAcked.emit(p1); + handleStanzaAcked(p1); } }); - stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() { + onAvailableChangedConnection = stanzaChannel_.onAvailableChanged.connect(new Slot1<Boolean>() { public void call(Boolean p1) { handleStanzaChannelAvailableChanged(p1); @@ -154,22 +165,29 @@ public class CoreClient { iqRouter_.setJID(jid); } - /*CoreClient::~CoreClient() { - if (session_ || connection_) { - std::cerr << "Warning: Client not disconnected properly" << std::endl; + protected void finalize() throws Throwable { + try { + forceReset(); + onAvailableChangedConnection.disconnect(); + onMessageReceivedConnection.disconnect(); + onPresenceReceivedConnection.disconnect(); + onStanzaAckedConnection.disconnect(); + stanzaChannel_ = null; + } + finally { + super.finalize(); + } } - delete tlsLayerFactory_; - delete timerFactory_; - delete connectionFactory_; - delete iqRouter_; - - stanzaChannel_->onAvailableChanged.disconnect(boost::bind(&CoreClient::handleStanzaChannelAvailableChanged, this, _1)); - stanzaChannel_->onMessageReceived.disconnect(boost::ref(onMessageReceived)); - stanzaChannel_->onPresenceReceived.disconnect(boost::ref(onPresenceReceived)); - stanzaChannel_->onStanzaAcked.disconnect(boost::ref(onStanzaAcked)); - delete stanzaChannel_; - }*/ + /** + * Connects the client to the server. + * + * After the connection is established, the client will set + * initialize the stream and authenticate. + */ + public void connect() { + connect(new ClientOptions()); + } /** * Connect using the standard XMPP connection rules (i.e. SRV then A/AAAA). @@ -177,33 +195,114 @@ public class CoreClient { * @param o Client options to use in the connection, must not be null */ public void connect(final ClientOptions o) { + logger_.fine("Connecting "); forceReset(); disconnectRequested_ = false; assert (connector_ == null); options = o; - /* FIXME: Port Proxies */ + + //TO PORT + /*// Determine connection types to use + assert(proxyConnectionFactories.isEmpty()); + boolean useDirectConnection = true; + HostAddressPort systemSOCKS5Proxy = networkFactories.getProxyProvider().getSOCKS5Proxy(); + HostAddressPort systemHTTPConnectProxy = networkFactories.getProxyProvider().getHTTPConnectProxy(); + switch (o.proxyType) { + case ClientOptions.ProxyType.NoProxy: + logger_.fine(" without a proxy\n"); + break; + case ClientOptions.ProxyType.SystemConfiguredProxy: + logger_.fine(" with a system configured proxy\n"); + if (systemSOCKS5Proxy.isValid()) { + logger_.fine("Found SOCK5 Proxy: " + systemSOCKS5Proxy.getAddress().toString() + ":" + systemSOCKS5Proxy.getPort() + "\n"); + proxyConnectionFactories.add(new SOCKS5ProxiedConnectionFactory(networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory(), systemSOCKS5Proxy.getAddress().toString(), systemSOCKS5Proxy.getPort())); + } + if (systemHTTPConnectProxy.isValid()) { + logger_.fine("Found HTTPConnect Proxy: " + systemHTTPConnectProxy.getAddress().toString() + ":" + systemHTTPConnectProxy.getPort() + "\n"); + proxyConnectionFactories.add(new HTTPConnectProxiedConnectionFactory(networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory(), systemHTTPConnectProxy.getAddress().toString(), systemHTTPConnectProxy.getPort())); + } + break; + case ClientOptions.ProxyType.SOCKS5Proxy: { + logger_.fine(" with manual configured SOCKS5 proxy\n"); + String proxyHostname = o.manualProxyHostname.empty() ? systemSOCKS5Proxy.getAddress().toString() : o.manualProxyHostname; + int proxyPort = o.manualProxyPort == -1 ? systemSOCKS5Proxy.getPort() : o.manualProxyPort; + logger_.fine("Proxy: " + proxyHostname + ":" + proxyPort + "\n"); + proxyConnectionFactories.add(new SOCKS5ProxiedConnectionFactory(networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory(), proxyHostname, proxyPort)); + useDirectConnection = false; + break; + } + case ClientOptions.ProxyType.HTTPConnectProxy: { + logger_.fine(" with manual configured HTTPConnect proxy\n"); + std::string proxyHostname = o.manualProxyHostname.empty() ? systemHTTPConnectProxy.getAddress().toString() : o.manualProxyHostname; + int proxyPort = o.manualProxyPort == -1 ? systemHTTPConnectProxy.getPort() : o.manualProxyPort; + logger_.fine("Proxy: " + proxyHostname + ":" << proxyPort + "\n"); + proxyConnectionFactories.add(new HTTPConnectProxiedConnectionFactory(networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory(), proxyHostname, proxyPort, o.httpTrafficFilter)); + useDirectConnection = false; + break; + } + } + Vector<ConnectionFactory> connectionFactories = new Vector<ConnectionFactory>(proxyConnectionFactories); + if (useDirectConnection) { + connectionFactories.add(networkFactories.getConnectionFactory()); + }*/ + String host = (o.manualHostname == null || o.manualHostname.isEmpty()) ? jid_.getDomain() : o.manualHostname; int port = o.manualPort; String serviceLookupPrefix = (o.manualHostname == null || o.manualHostname.isEmpty() ? "_xmpp-client._tcp." : null); + assert(connector_ == null); + //TO PORT + /*if (options.boshURL.isEmpty()) { + connector_ = new ChainedConnector(host, port, serviceLookupPrefix, networkFactories.getDomainNameResolver(), connectionFactories, networkFactories.getTimerFactory()); + connector_.onConnectFinished.connect(boost::bind(&CoreClient::handleConnectorFinished, this, _1, _2)); + connector_.setTimeoutMilliseconds(2*60*1000); + connector_.start(); + } + else { + /* Autodiscovery of which proxy works is largely ok with a TCP session, because this is a one-off. With BOSH + * it would be quite painful given that potentially every stanza could be sent on a new connection. + */ + //sessionStream_ = boost::make_shared<BOSHSessionStream>(boost::make_shared<BOSHConnectionFactory>(options.boshURL, networkFactories.getConnectionFactory(), networkFactories.getXMLParserFactory(), networkFactories.getTLSContextFactory()), getPayloadParserFactories(), getPayloadSerializers(), networkFactories.getTLSContextFactory(), networkFactories.getTimerFactory(), networkFactories.getXMLParserFactory(), networkFactories.getEventLoop(), host, options.boshHTTPConnectProxyURL, options.boshHTTPConnectProxyAuthID, options.boshHTTPConnectProxyAuthPassword); + /*sessionStream_ = boost::shared_ptr<BOSHSessionStream>(new BOSHSessionStream( + options.boshURL, + getPayloadParserFactories(), + getPayloadSerializers(), + networkFactories.getConnectionFactory(), + networkFactories.getTLSContextFactory(), + networkFactories.getTimerFactory(), + networkFactories.getXMLParserFactory(), + networkFactories.getEventLoop(), + networkFactories.getDomainNameResolver(), + host, + options.boshHTTPConnectProxyURL, + options.boshHTTPConnectProxyAuthID, + options.boshHTTPConnectProxyAuthPassword, + options.tlsOptions)); + sessionStream_.onDataRead.connect(boost::bind(&CoreClient::handleDataRead, this, _1)); + sessionStream_.onDataWritten.connect(boost::bind(&CoreClient::handleDataWritten, this, _1)); + bindSessionToStream(); + }*/ connector_ = Connector.create(host, port, serviceLookupPrefix, networkFactories.getDomainNameResolver(), networkFactories.getConnectionFactory(), networkFactories.getTimerFactory()); connectorConnectFinishedConnection_ = connector_.onConnectFinished.connect(new Slot2<Connection, com.isode.stroke.base.Error>() { public void call(Connection p1, com.isode.stroke.base.Error p2) { handleConnectorFinished(p1, p2); } }); - connector_.setTimeoutMilliseconds(60 * 1000); + connector_.setTimeoutMilliseconds(2*60*1000); connector_.start(); } private void bindSessionToStream() { - session_ = ClientSession.create(jid_, sessionStream_); + //TO PORT + //session_ = ClientSession::create(jid_, sessionStream_, networkFactories.getIDNConverter(), networkFactories.getCryptoProvider()); + session_ = ClientSession.create(jid_, sessionStream_, new ICUConverter(), new JavaCryptoProvider()); session_.setCertificateTrustChecker(certificateTrustChecker); session_.setUseStreamCompression(options.useStreamCompression); session_.setAllowPLAINOverNonTLS(options.allowPLAINWithoutTLS); + session_.setSingleSignOn(options.singleSignOn); + session_.setAuthenticationPort(options.manualPort); switch (options.useTLS) { case UseTLSWhenAvailable: session_.setUseTLS(ClientSession.UseTLS.UseTLSWhenAvailable); - session_.setCertificateTrustChecker(certificateTrustChecker); break; case NeverUseTLS: session_.setUseTLS(ClientSession.UseTLS.NeverUseTLS); @@ -229,10 +328,13 @@ public class CoreClient { session_.start(); } - void handleConnectorFinished(final Connection connection, final com.isode.stroke.base.Error error) { + private void handleConnectorFinished(final Connection connection, final com.isode.stroke.base.Error error) { resetConnector(); if (connection == null) { + if (options.forgetPassword) { + purgePassword(); + } ClientError clientError = null; if (!disconnectRequested_) { if (error instanceof DomainNameResolveError) { @@ -242,7 +344,8 @@ public class CoreClient { } } onDisconnected.emit(clientError); - } else { + } + else { assert (connection_ == null); connection_ = connection; @@ -284,7 +387,12 @@ public class CoreClient { connector_.stop(); } } - + + /** + * Checks whether the client is active. + * + * A client is active when it is connected or connecting to the server. + */ public boolean isActive() { return (session_ != null && !session_.isFinished()) || connector_ != null; } @@ -308,19 +416,14 @@ public class CoreClient { } private void handleSessionFinished(final com.isode.stroke.base.Error error) { - sessionFinishedConnection_.disconnect(); - sessionNeedCredentialsConnection_.disconnect(); - session_ = null; - - sessionStreamDataReadConnection_.disconnect(); - sessionStreamDataWrittenConnection_.disconnect(); - sessionStream_ = null; - - connection_.disconnect(); - connection_ = null; + if (options.forgetPassword) { + purgePassword(); + } + resetSession(); - ClientError clientError = null; + ClientError actualerror = null; if (error != null) { + ClientError clientError = null; if (error instanceof ClientSession.Error) { ClientSession.Error actualError = (ClientSession.Error) error; switch (actualError.type) { @@ -351,9 +454,23 @@ public class CoreClient { case TLSClientCertificateError: clientError = new ClientError(ClientError.Type.ClientCertificateError); break; - /* Note: no case clause for "StreamError" */ + case StreamError: + clientError = new ClientError(ClientError.Type.StreamError); + break; } - } else if (error instanceof SessionStream.SessionStreamError) { + } + else if (error instanceof TLSError) { + TLSError actualError = (TLSError)error; + switch(actualError.getType()) { + case CertificateCardRemoved: + clientError = new ClientError(ClientError.Type.CertificateCardRemoved); + break; + case UnknownError: + clientError = new ClientError(ClientError.Type.TLSError); + break; + } + } + else if (error instanceof SessionStream.SessionStreamError) { SessionStream.SessionStreamError actualError = (SessionStream.SessionStreamError) error; switch (actualError.type) { case ParseError: @@ -408,19 +525,29 @@ public class CoreClient { case InvalidServerIdentity: clientError = new ClientError(ClientError.Type.InvalidServerIdentityError); break; + case Revoked: + clientError = new ClientError(ClientError.Type.RevokedError); + break; + case RevocationCheckFailed: + clientError = new ClientError(ClientError.Type.RevocationCheckFailedError); + break; } } /* If "error" was non-null, we expect to be able to derive * a non-null "clientError". */ NotNull.exceptIfNull(clientError,"clientError"); + actualerror = clientError; } - onDisconnected.emit(clientError); + onDisconnected.emit(actualerror); } private void handleNeedCredentials() { assert session_ != null; session_.sendCredentials(password_); + if (options.forgetPassword) { + purgePassword(); + } } private void handleDataRead(final SafeByteArray data) { @@ -431,8 +558,26 @@ public class CoreClient { onDataWritten.emit(data); } + private void handlePresenceReceived(Presence presence) { + onPresenceReceived.emit(presence); + } + + private void handleMessageReceived(Message message) { + onMessageReceived.emit(message); + } + + private void handleStanzaAcked(Stanza stanza) { + onStanzaAcked.emit(stanza); + } + + private void purgePassword() { + password_ = new SafeByteArray(); + } + private void handleStanzaChannelAvailableChanged(final boolean available) { if (available) { + iqRouter_.setJID(session_.getLocalJID()); + handleConnected(); onConnected.emit(); } } @@ -446,6 +591,13 @@ public class CoreClient { } /** + * Sends raw, unchecked data. + */ + public void sendData(final String data) { + sessionStream_.writeData(data); + } + + /** * Get the IQRouter responsible for all IQs on this connection. * Use this to send IQs. */ @@ -458,6 +610,8 @@ public class CoreClient { } /** + * Checks whether the client is connected to the server, + * and stanzas can be sent. * @return session is available for sending/receiving stanzas. */ public boolean isAvailable() { @@ -478,12 +632,26 @@ public class CoreClient { connectorConnectFinishedConnection_.disconnect(); } connector_ = null; + //TO PORT + /*for(ConnectionFactory* f, proxyConnectionFactories) { + delete f; + } + proxyConnectionFactories.clear();*/ } protected ClientSession getSession() { return session_; } + protected NetworkFactories getNetworkFactories() { + return networkFactories; + } + + /** + * Called before onConnected signal is emmitted. + */ + protected void handleConnected() {} + private void resetSession() { session_.onFinished.disconnectAll(); session_.onNeedCredentials.disconnectAll(); @@ -494,6 +662,10 @@ public class CoreClient { if (connection_ != null) { connection_.disconnect(); } + // TO PORT + /*else if (sessionStream_ instanceof BOSHSessionStream) { + sessionStream_.close(); + }*/ sessionStream_ = null; connection_ = null; } @@ -512,11 +684,25 @@ public class CoreClient { } } + /** + * Checks whether stream management is enabled. + * + * If stream management is enabled, onStanzaAcked will be + * emitted when a stanza is received by the server. + * + * \see onStanzaAcked + */ + public boolean getStreamManagementEnabled() { + return stanzaChannel_.getStreamManagementEnabled(); + } + private void forceReset() { if (connector_ != null) { + logger_.warning("Client not disconnected properly: Connector still active\n"); resetConnector(); } if (sessionStream_ != null || connection_ != null) { + logger_.warning("Client not disconnected properly: Session still active\n"); resetSession(); } diff --git a/src/com/isode/stroke/client/DummyNickManager.java b/src/com/isode/stroke/client/DummyNickManager.java new file mode 100644 index 0000000..ee7e67b --- /dev/null +++ b/src/com/isode/stroke/client/DummyNickManager.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.client; + +public class DummyNickManager extends NickManager { + + public String getOwnNick() { + return ""; + } + + public void setOwnNick(final String nick) { + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/client/IDGenerator.java b/src/com/isode/stroke/client/IDGenerator.java deleted file mode 100644 index c7bb494..0000000 --- a/src/com/isode/stroke/client/IDGenerator.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2010, Isode Limited, London, England. - * All rights reserved. - */ -/* - * Copyright (c) 2010, Remko Tronçon. - * All rights reserved. - */ - -package com.isode.stroke.client; - -/** - * - */ -public class IDGenerator { - private int next_ = 42; - public String generateID() { - next_++; - return String.valueOf(next_); - - } -} diff --git a/src/com/isode/stroke/client/MemoryStorages.java b/src/com/isode/stroke/client/MemoryStorages.java index 16d235d..bafd480 100644 --- a/src/com/isode/stroke/client/MemoryStorages.java +++ b/src/com/isode/stroke/client/MemoryStorages.java @@ -14,8 +14,12 @@ import com.isode.stroke.roster.RosterStorage; import com.isode.stroke.vcards.VCardMemoryStorage; import com.isode.stroke.vcards.VCardStorage; +/** + * An implementation of Storages for storing all + * controller data in memory. + */ public class MemoryStorages implements Storages { - private VCardStorage vcardStorage; + private VCardMemoryStorage vcardStorage; private AvatarStorage avatarStorage; private CapsStorage capsStorage; private RosterStorage rosterStorage; @@ -53,4 +57,8 @@ public class MemoryStorages implements Storages { return avatarStorage; } + /*@Override + public HistoryStorage getHistoryStorage() { + return historyStorage; + }*/ } diff --git a/src/com/isode/stroke/client/NickManagerImpl.java b/src/com/isode/stroke/client/NickManagerImpl.java index 17f9d6b..8a2ebed 100644 --- a/src/com/isode/stroke/client/NickManagerImpl.java +++ b/src/com/isode/stroke/client/NickManagerImpl.java @@ -13,11 +13,12 @@ import com.isode.stroke.vcards.VCardManager; public class NickManagerImpl extends NickManager { private JID ownJID = new JID(); private String ownNick = ""; + private VCardManager vcardManager; private SignalConnection vCardChangedSignal; - NickManagerImpl(final JID ownJID, VCardManager vcardManager) { + public NickManagerImpl(final JID ownJID, VCardManager vcardManager) { this.ownJID = ownJID; - + this.vcardManager = vcardManager; vCardChangedSignal = vcardManager.onVCardChanged.connect(new Slot2<JID, VCard>() { @Override public void call(JID p1, VCard p2) { @@ -53,7 +54,7 @@ public class NickManagerImpl extends NickManager { if (vcard != null && !vcard.getNickname().isEmpty()) { nick = vcard.getNickname(); } - if (ownNick != nick && nick != null && !nick.equals(ownNick)) { + if (nick != ownNick && nick != null && !nick.equals(ownNick)) { ownNick = nick; onOwnNickChanged.emit(ownNick); } diff --git a/src/com/isode/stroke/client/NickResolver.java b/src/com/isode/stroke/client/NickResolver.java index fe2b129..470e4b9 100644 --- a/src/com/isode/stroke/client/NickResolver.java +++ b/src/com/isode/stroke/client/NickResolver.java @@ -16,14 +16,19 @@ import com.isode.stroke.signals.Slot2; import com.isode.stroke.signals.Slot3; import com.isode.stroke.vcards.VCardManager; +// FIXME: The NickResolver currently relies on the vcard being requested by the client on login. +// The VCardManager should get an onConnected() signal (which is signalled when the stanzachannel is available(, and each time this is emitted, +// the nickresolver should request the vcard. +// FIXME: The ownJID functionality should probably be removed, and NickManager should be used directly. + public class NickResolver { - private JID ownJID_; - private String ownNick_; + private JID ownJID_ = new JID(); + private String ownNick_ = ""; private XMPPRoster xmppRoster_; private MUCRegistry mucRegistry_; private VCardManager vcardManager_; - public final Signal2<JID, String> onNickChanged = new Signal2<JID, String>(); + public final Signal2<JID, String /*previousNick*/ > onNickChanged = new Signal2<JID, String>(); public NickResolver(final JID ownJID, XMPPRoster xmppRoster, VCardManager vcardManager, MUCRegistry mucRegistry) { ownJID_ = ownJID; @@ -57,7 +62,7 @@ public class NickResolver { } void handleJIDAdded(final JID jid) { - String oldNick= jidToNick(jid); + String oldNick = jidToNick(jid); onNickChanged.emit(jid, oldNick); } @@ -67,7 +72,7 @@ public class NickResolver { return ownNick_; } } - + String nick = ""; if (mucRegistry_ != null && mucRegistry_.isMUC(jid.toBare()) ) { return jid.getResource().isEmpty() ? jid.toBare().toString() : jid.getResource(); } @@ -83,6 +88,7 @@ public class NickResolver { if (jid.compare(ownJID_, JID.CompareType.WithoutResource) != 0) { return; } + String initialNick = ownNick_; ownNick_ = ownJID_.toString(); if (ownVCard != null) { if (!ownVCard.getNickname().isEmpty()) { diff --git a/src/com/isode/stroke/client/StanzaChannel.java b/src/com/isode/stroke/client/StanzaChannel.java index 5a1d270..253f026 100644 --- a/src/com/isode/stroke/client/StanzaChannel.java +++ b/src/com/isode/stroke/client/StanzaChannel.java @@ -32,7 +32,7 @@ public abstract class StanzaChannel extends IQChannel { public final Signal1<Message> onMessageReceived = new Signal1<Message>(); public final Signal1<Presence> onPresenceReceived = new Signal1<Presence>(); - public final Signal1<Boolean> onAvailableChanged = new Signal1<Boolean>(); + public final Signal1<Boolean /* isAvailable */ > onAvailableChanged = new Signal1<Boolean>(); public final Signal1<Stanza> onStanzaAcked = new Signal1<Stanza>(); diff --git a/src/com/isode/stroke/client/Storages.java b/src/com/isode/stroke/client/Storages.java index 25821e5..24ffff4 100644 --- a/src/com/isode/stroke/client/Storages.java +++ b/src/com/isode/stroke/client/Storages.java @@ -9,6 +9,10 @@ import com.isode.stroke.disco.CapsStorage; import com.isode.stroke.roster.RosterStorage; import com.isode.stroke.vcards.VCardStorage; +/** + * An interface to hold storage classes for different + * controllers. + */ public interface Storages { VCardStorage getVCardStorage(); AvatarStorage getAvatarStorage(); diff --git a/src/com/isode/stroke/client/XMLBeautifier.java b/src/com/isode/stroke/client/XMLBeautifier.java new file mode 100644 index 0000000..7f6e346 --- /dev/null +++ b/src/com/isode/stroke/client/XMLBeautifier.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ +/* + * Copyright (c) 2014-2015 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ +/* + * Copyright (c) 2015 Tarun Gupta. + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +package com.isode.stroke.client; + +import java.util.Stack; +import com.isode.stroke.parser.XMLParserClient; +import com.isode.stroke.parser.XMLParser; +import com.isode.stroke.parser.AttributeMap; +import com.isode.stroke.parser.PlatformXMLParserFactory; + +public class XMLBeautifier implements XMLParserClient { + + private boolean doIndention; + private boolean doColoring; + + private int intLevel; + private String inputBuffer = ""; + private StringBuffer buffer = new StringBuffer(); + private XMLParser parser; + + private boolean lastWasStepDown; + private Stack<String> parentNSs = new Stack<String>(); + + // all bold but reset + public static final String colorReset = "\u001B[0m"; + public static final String ANSI_BLACK = "\u001B[30m"; + public static final String colorRed = "\u001B[31m"; + public static final String colorGreen = "\u001B[32m"; + public static final String colorYellow = "\u001B[33m"; + public static final String colorBlue = "\u001B[34m"; + public static final String ANSI_PURPLE = "\u001B[35m"; + public static final String colorCyan = "\u001B[36m"; + public static final String ANSI_WHITE = "\u001B[37m"; + + public XMLBeautifier(boolean indention, boolean coloring) { + this.doIndention = indention; + this.doColoring = coloring; + intLevel = 0; + parser = null; + lastWasStepDown = false; + } + + public String beautify(final String text) { + parser = PlatformXMLParserFactory.createXMLParser(this); + intLevel = 0; + buffer.append(""); + parser.parse(text); + parser = null; + return buffer.toString(); + } + + public void handleStartElement(final String element, final String ns, final AttributeMap attributes) { + if (doIndention) { + if (intLevel != 0) buffer.append("\n"); + } + indent(); + buffer.append("<").append(doColoring ? styleTag(element) : element); + if (!ns.isEmpty() && (!parentNSs.isEmpty() && !parentNSs.peek().equals(ns))) { + buffer.append(" "); + buffer.append((doColoring ? styleAttribute("xmlns") : "xmlns")); + buffer.append("="); + buffer.append("\"").append((doColoring ? styleNamespace(ns) : ns)).append("\""); + } + if (!attributes.getEntries().isEmpty()) { + for(AttributeMap.Entry entry : attributes.getEntries()) { + buffer.append(" "); + buffer.append((doColoring ? styleAttribute(entry.getAttribute().getName()) : entry.getAttribute().getName())); + buffer.append("="); + buffer.append("\"").append((doColoring ? styleValue(entry.getValue()) : entry.getValue())).append("\""); + } + } + buffer.append(">"); + ++intLevel; + lastWasStepDown = false; + parentNSs.push(ns); + } + + public void handleEndElement(final String element, final String ns) { + --intLevel; + parentNSs.pop(); + if (/*hadCDATA.top() ||*/ lastWasStepDown) { + if (doIndention) { + buffer.append("\n"); + } + indent(); + } + buffer.append("</").append((doColoring ? styleTag(element) : element)).append(">"); + lastWasStepDown = true; + } + + public void handleCharacterData(final String data) { + buffer.append(data); + lastWasStepDown = false; + } + + private void indent() { + for (int i = 0; i < intLevel; ++i) { + buffer.append(" "); + } + } + + private String styleTag(final String text) { + String result = ""; + result += colorYellow; + result += text; + result += colorReset; + return result; + } + + private String styleNamespace(final String text) { + String result = ""; + result += colorRed; + result += text; + result += colorReset; + return result; + } + + private String styleAttribute(final String text) { + String result = ""; + result += colorGreen; + result += text; + result += colorReset; + return result; + } + + private String styleValue(final String text) { + String result = ""; + result += colorCyan; + result += text; + result += colorReset; + return result; + } +}
\ No newline at end of file diff --git a/src/com/isode/stroke/crypto/JavaCryptoProvider.java b/src/com/isode/stroke/crypto/JavaCryptoProvider.java index 1353225..98eee89 100644 --- a/src/com/isode/stroke/crypto/JavaCryptoProvider.java +++ b/src/com/isode/stroke/crypto/JavaCryptoProvider.java @@ -139,6 +139,6 @@ public class JavaCryptoProvider extends CryptoProvider { @Override public boolean isMD5AllowedForCrypto() { - return false; + return true; } }
\ No newline at end of file diff --git a/src/com/isode/stroke/disco/ClientDiscoManager.java b/src/com/isode/stroke/disco/ClientDiscoManager.java index ba620ed..52a31f2 100644 --- a/src/com/isode/stroke/disco/ClientDiscoManager.java +++ b/src/com/isode/stroke/disco/ClientDiscoManager.java @@ -69,7 +69,7 @@ public class ClientDiscoManager { * Called when the client is connected. This resets the presence sender, * such that it assumes initial presence hasn't been sent yet. */ - void handleConnected() { + public void handleConnected() { presenceSender.reset(); } |