2017-06-23 22:48:28 +02:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* Copyright 2017 Paul Schaub
|
|
|
|
*
|
|
|
|
* 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.smackx.jingle.transports.jingle_s5b;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.net.Socket;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
import java.util.logging.Level;
|
|
|
|
import java.util.logging.Logger;
|
|
|
|
|
|
|
|
import org.jivesoftware.smack.SmackException;
|
|
|
|
import org.jivesoftware.smack.XMPPException;
|
|
|
|
import org.jivesoftware.smack.packet.IQ;
|
2017-06-25 14:25:17 +02:00
|
|
|
import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession;
|
2017-06-23 22:48:28 +02:00
|
|
|
import org.jivesoftware.smackx.bytestreams.socks5.Socks5Client;
|
2017-06-25 14:25:17 +02:00
|
|
|
import org.jivesoftware.smackx.bytestreams.socks5.Socks5ClientForInitiator;
|
2017-06-23 22:48:28 +02:00
|
|
|
import org.jivesoftware.smackx.bytestreams.socks5.Socks5Utils;
|
|
|
|
import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
|
|
|
|
import org.jivesoftware.smackx.jingle.JingleManager;
|
|
|
|
import org.jivesoftware.smackx.jingle.JingleSession;
|
2017-06-25 15:13:56 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.JingleUtil;
|
2017-06-23 22:48:28 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.element.Jingle;
|
2017-06-24 17:46:03 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.element.JingleContent;
|
2017-06-23 22:48:28 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.element.JingleContentTransportCandidate;
|
|
|
|
import org.jivesoftware.smackx.jingle.transports.JingleTransportInitiationCallback;
|
2017-06-25 14:25:17 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.transports.JingleTransportInitiationException;
|
2017-06-23 22:48:28 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.transports.JingleTransportManager;
|
|
|
|
import org.jivesoftware.smackx.jingle.transports.JingleTransportSession;
|
|
|
|
import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport;
|
|
|
|
import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransportCandidate;
|
2017-06-25 14:25:17 +02:00
|
|
|
import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransportInfo;
|
2017-06-23 22:48:28 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* LOL.
|
|
|
|
*/
|
|
|
|
public class JingleS5BTransportSession extends JingleTransportSession<JingleS5BTransport> {
|
|
|
|
private static final Logger LOGGER = Logger.getLogger(JingleS5BTransportSession.class.getName());
|
|
|
|
private final JingleS5BTransportManager transportManager;
|
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
private final JingleUtil jutil;
|
2017-06-24 17:46:03 +02:00
|
|
|
private Socket connectedSocket;
|
2017-06-25 14:25:17 +02:00
|
|
|
private JingleS5BTransportCandidate localUsedCandidate;
|
|
|
|
private JingleS5BTransportCandidate remoteUsedCandidate;
|
|
|
|
private JingleTransportInitiationCallback callback;
|
|
|
|
private boolean remoteError = false;
|
|
|
|
private boolean localError = false;
|
2017-06-24 17:46:03 +02:00
|
|
|
|
2017-06-23 22:48:28 +02:00
|
|
|
public JingleS5BTransportSession(JingleSession jingleSession) {
|
|
|
|
super(jingleSession);
|
|
|
|
transportManager = JingleS5BTransportManager.getInstanceFor(jingleSession.getConnection());
|
2017-06-25 15:13:56 +02:00
|
|
|
jutil = new JingleUtil(jingleSession.getConnection());
|
2017-06-23 22:48:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public JingleS5BTransport createTransport() {
|
|
|
|
if (localTransport != null) {
|
|
|
|
return (JingleS5BTransport) localTransport;
|
|
|
|
}
|
|
|
|
|
|
|
|
return createTransport(JingleManager.randomId(), Bytestream.Mode.tcp);
|
|
|
|
}
|
|
|
|
|
|
|
|
private JingleS5BTransport createTransport(String sid, Bytestream.Mode mode) {
|
2017-06-25 14:25:17 +02:00
|
|
|
JingleSession jSession = jingleSession.get();
|
|
|
|
if (jSession == null) {
|
|
|
|
throw new NullPointerException("Lost reference to JingleSession.");
|
|
|
|
}
|
|
|
|
|
2017-06-23 22:48:28 +02:00
|
|
|
JingleS5BTransport.Builder builder = JingleS5BTransport.getBuilder();
|
|
|
|
|
|
|
|
for (Bytestream.StreamHost host : transportManager.getLocalStreamHosts()) {
|
|
|
|
JingleS5BTransportCandidate candidate = new JingleS5BTransportCandidate(host, 100);
|
|
|
|
builder.addTransportCandidate(candidate);
|
|
|
|
}
|
|
|
|
|
|
|
|
List<Bytestream.StreamHost> availableStreamHosts = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
availableStreamHosts = transportManager.getAvailableStreamHosts();
|
2017-06-25 14:25:17 +02:00
|
|
|
} catch (XMPPException.XMPPErrorException | SmackException.NoResponseException | InterruptedException |
|
|
|
|
SmackException.NotConnectedException e) {
|
2017-06-23 22:48:28 +02:00
|
|
|
LOGGER.log(Level.WARNING, "Could not get available StreamHosts: " + e, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Bytestream.StreamHost host : availableStreamHosts != null ?
|
|
|
|
availableStreamHosts : Collections.<Bytestream.StreamHost>emptyList()) {
|
|
|
|
JingleS5BTransportCandidate candidate = new JingleS5BTransportCandidate(host, 0);
|
|
|
|
builder.addTransportCandidate(candidate);
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.setStreamId(sid);
|
|
|
|
builder.setMode(mode);
|
2017-06-25 14:25:17 +02:00
|
|
|
builder.setDestinationAddress(Socks5Utils.createDigest(sid, jSession.getLocal(), jSession.getRemote()));
|
2017-06-23 22:48:28 +02:00
|
|
|
return builder.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void initiateOutgoingSession(JingleTransportInitiationCallback callback) {
|
2017-06-25 14:25:17 +02:00
|
|
|
this.callback = callback;
|
|
|
|
initiateSession();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void initiateIncomingSession(JingleTransportInitiationCallback callback) {
|
|
|
|
this.callback = callback;
|
|
|
|
initiateSession();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void initiateSession() {
|
2017-06-24 17:46:03 +02:00
|
|
|
JingleSession jSession = jingleSession.get();
|
|
|
|
if (jSession == null) {
|
|
|
|
throw new NullPointerException("Lost reference to jingleSession.");
|
|
|
|
}
|
|
|
|
|
2017-06-23 22:48:28 +02:00
|
|
|
JingleS5BTransport receivedTransport = (JingleS5BTransport) remoteTransport;
|
|
|
|
|
|
|
|
Socket socket = null;
|
|
|
|
JingleS5BTransportCandidate workedForUs = null;
|
|
|
|
|
|
|
|
for (JingleContentTransportCandidate c : receivedTransport.getCandidates()) {
|
|
|
|
JingleS5BTransportCandidate candidate = (JingleS5BTransportCandidate) c;
|
|
|
|
Bytestream.StreamHost streamHost = candidate.getStreamHost();
|
|
|
|
|
|
|
|
String address = streamHost.getAddress();
|
|
|
|
|
|
|
|
try {
|
|
|
|
Socks5Client socks5Client = new Socks5Client(streamHost, receivedTransport.getDestinationAddress());
|
|
|
|
socket = socks5Client.getSocket(10 * 1000);
|
|
|
|
workedForUs = candidate;
|
|
|
|
|
|
|
|
} catch (IOException | XMPPException | InterruptedException | TimeoutException | SmackException e) {
|
|
|
|
LOGGER.log(Level.WARNING, "Could not connect to remotes address " + address + " with dstAddr "
|
|
|
|
+ receivedTransport.getDestinationAddress());
|
|
|
|
}
|
|
|
|
|
2017-06-24 17:46:03 +02:00
|
|
|
JingleContent content = jSession.getContents().get(0);
|
|
|
|
|
|
|
|
Jingle response;
|
|
|
|
|
2017-06-23 22:48:28 +02:00
|
|
|
if (socket != null) {
|
2017-06-24 17:46:03 +02:00
|
|
|
connectedSocket = socket;
|
2017-06-25 14:25:17 +02:00
|
|
|
localUsedCandidate = workedForUs;
|
2017-06-23 22:48:28 +02:00
|
|
|
|
2017-06-24 17:46:03 +02:00
|
|
|
response = transportManager.createCandidateUsed(jSession.getRemote(),
|
|
|
|
jSession.getSessionId(), content.getSenders(), content.getCreator(),
|
2017-06-25 14:25:17 +02:00
|
|
|
content.getName(), receivedTransport.getStreamId(), localUsedCandidate.getCandidateId());
|
2017-06-24 17:46:03 +02:00
|
|
|
|
|
|
|
} else {
|
2017-06-25 14:25:17 +02:00
|
|
|
localError = true;
|
2017-06-24 17:46:03 +02:00
|
|
|
response = transportManager.createCandidateError(jSession.getRemote(),
|
|
|
|
jSession.getSessionId(), content.getSenders(), content.getCreator(),
|
|
|
|
content.getName(), receivedTransport.getStreamId());
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
jSession.getConnection().sendStanza(response);
|
|
|
|
} catch (SmackException.NotConnectedException | InterruptedException e) {
|
|
|
|
LOGGER.log(Level.WARNING, "Could not send candidate-used.", e);
|
2017-06-23 22:48:28 +02:00
|
|
|
}
|
2017-06-25 14:25:17 +02:00
|
|
|
|
|
|
|
closeIfBothSidesFailed();
|
2017-06-23 22:48:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-25 14:25:17 +02:00
|
|
|
private boolean closeIfBothSidesFailed() {
|
|
|
|
JingleSession jSession = jingleSession.get();
|
|
|
|
if (jSession != null) {
|
|
|
|
if (localError && remoteError) {
|
|
|
|
callback.onException(new JingleTransportInitiationException.CandidateError());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2017-06-25 15:13:56 +02:00
|
|
|
return false;
|
2017-06-25 14:25:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private JingleS5BTransportCandidate determineUsedCandidate() {
|
|
|
|
if (localUsedCandidate == null && remoteUsedCandidate == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remoteUsedCandidate == null) {
|
|
|
|
return localUsedCandidate;
|
|
|
|
}
|
2017-06-23 22:48:28 +02:00
|
|
|
|
2017-06-25 14:25:17 +02:00
|
|
|
if (localUsedCandidate == null) {
|
|
|
|
return remoteUsedCandidate;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (localUsedCandidate.getPriority() > remoteUsedCandidate.getPriority()) {
|
|
|
|
return localUsedCandidate;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (localUsedCandidate.getPriority() < remoteUsedCandidate.getPriority()) {
|
|
|
|
return remoteUsedCandidate;
|
|
|
|
}
|
|
|
|
|
|
|
|
return jingleSession.get().isInitiator() ? localUsedCandidate : remoteUsedCandidate;
|
|
|
|
}
|
|
|
|
|
|
|
|
public IQ handleCandidateUsed(Jingle candidateUsed) {
|
|
|
|
JingleS5BTransportInfo info = (JingleS5BTransportInfo) candidateUsed.getContents().get(0)
|
|
|
|
.getJingleTransport().getInfos().get(0);
|
|
|
|
|
|
|
|
String candidateId = ((JingleS5BTransportInfo.CandidateUsed) info).getCandidateId();
|
|
|
|
|
|
|
|
for (JingleContentTransportCandidate c : localTransport.getCandidates()) {
|
|
|
|
JingleS5BTransportCandidate candidate = (JingleS5BTransportCandidate) c;
|
|
|
|
if (candidate.getCandidateId().equals(candidateId)) {
|
|
|
|
remoteUsedCandidate = candidate;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remoteUsedCandidate == null) {
|
|
|
|
callback.onException(new Exception("Unknown candidate"));
|
2017-06-25 15:13:56 +02:00
|
|
|
return jutil.createErrorMalformedRequest(candidateUsed);
|
2017-06-25 14:25:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (localUsedCandidate != null) {
|
2017-06-25 15:13:56 +02:00
|
|
|
connect(determineUsedCandidate());
|
|
|
|
}
|
2017-06-25 14:25:17 +02:00
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
return IQ.createResultIQ(candidateUsed);
|
|
|
|
}
|
2017-06-25 14:25:17 +02:00
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
private void connect(JingleS5BTransportCandidate candidate) {
|
|
|
|
JingleSession jSession = jingleSession.get();
|
|
|
|
if (jSession == null) {
|
|
|
|
throw new NullPointerException("Lost reference to JingleSession.");
|
|
|
|
}
|
|
|
|
// Used candidate belongs to remote.
|
|
|
|
if (candidate == localUsedCandidate) {
|
2017-06-25 14:25:17 +02:00
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
if (connectedSocket != null) {
|
|
|
|
callback.onSessionInitiated(new Socks5BytestreamSession(connectedSocket,
|
|
|
|
candidate.getJid().asBareJid().equals(jSession.getRemote().asBareJid())));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
throw new AssertionError("Connected socket is null.");
|
|
|
|
}
|
|
|
|
}
|
2017-06-25 14:25:17 +02:00
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
// Used candidate belongs to us.
|
|
|
|
else {
|
2017-06-25 14:25:17 +02:00
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
if (candidate.getType() == JingleS5BTransportCandidate.Type.proxy) {
|
2017-06-25 14:25:17 +02:00
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
if (candidate.getJid().asBareJid().equals(jSession.getLocal().asBareJid())) {
|
|
|
|
|
|
|
|
Socks5ClientForInitiator socks5Client = new Socks5ClientForInitiator(candidate.getStreamHost(),
|
|
|
|
((JingleS5BTransport) localTransport).getDestinationAddress(),
|
|
|
|
jSession.getConnection(), ((JingleS5BTransport) localTransport).getStreamId(),
|
|
|
|
jSession.getLocal());
|
|
|
|
try {
|
|
|
|
connectedSocket = socks5Client.getSocket(10 * 1000);
|
|
|
|
} catch (IOException | XMPPException | SmackException | InterruptedException | TimeoutException e) {
|
|
|
|
callback.onException(e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
callback.onSessionInitiated(new Socks5BytestreamSession(connectedSocket, true));
|
2017-06-25 14:25:17 +02:00
|
|
|
} else {
|
2017-06-25 15:13:56 +02:00
|
|
|
Bytestream activateProxy = new Bytestream(((JingleS5BTransport) localTransport).getStreamId());
|
|
|
|
activateProxy.setToActivate(candidate.getJid());
|
|
|
|
activateProxy.setTo(candidate.getJid());
|
|
|
|
try {
|
|
|
|
jSession.getConnection().createStanzaCollectorAndSend(activateProxy).nextResultOrThrow();
|
|
|
|
} catch (SmackException.NoResponseException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | InterruptedException e) {
|
|
|
|
LOGGER.log(Level.SEVERE, "Could not activate proxy server: " + e, e);
|
|
|
|
|
|
|
|
}
|
2017-06-25 14:25:17 +02:00
|
|
|
}
|
2017-06-25 15:13:56 +02:00
|
|
|
|
|
|
|
} else {
|
|
|
|
|
2017-06-25 14:25:17 +02:00
|
|
|
}
|
|
|
|
}
|
2017-06-25 15:13:56 +02:00
|
|
|
}
|
2017-06-25 14:25:17 +02:00
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
public IQ handleCandidateActivated(Jingle candidateActivated) {
|
|
|
|
|
|
|
|
return IQ.createResultIQ(candidateActivated);
|
|
|
|
}
|
|
|
|
|
|
|
|
public IQ handleCandidateError(Jingle candidateError) {
|
|
|
|
remoteError = true;
|
|
|
|
|
|
|
|
closeIfBothSidesFailed();
|
|
|
|
|
|
|
|
if (localUsedCandidate.getType() != JingleS5BTransportCandidate.Type.proxy) {
|
|
|
|
//TODO: Connect
|
|
|
|
} else {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return IQ.createResultIQ(candidateError);
|
|
|
|
}
|
|
|
|
|
|
|
|
public IQ handleProxyError(Jingle proxyError) {
|
|
|
|
|
|
|
|
return IQ.createResultIQ(proxyError);
|
2017-06-23 22:48:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getNamespace() {
|
|
|
|
return transportManager.getNamespace();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public IQ handleTransportInfo(Jingle transportInfo) {
|
2017-06-25 14:25:17 +02:00
|
|
|
JingleS5BTransport transport = (JingleS5BTransport) transportInfo.getContents().get(0).getJingleTransport();
|
|
|
|
JingleS5BTransportInfo info = (JingleS5BTransportInfo) transport.getInfos().get(0);
|
2017-06-25 15:13:56 +02:00
|
|
|
|
2017-06-25 14:25:17 +02:00
|
|
|
if (info != null) {
|
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
switch (info.getElementName()) {
|
2017-06-25 14:25:17 +02:00
|
|
|
case JingleS5BTransportInfo.CandidateUsed.ELEMENT:
|
|
|
|
return handleCandidateUsed(transportInfo);
|
|
|
|
|
|
|
|
case JingleS5BTransportInfo.CandidateActivated.ELEMENT:
|
2017-06-25 15:13:56 +02:00
|
|
|
return handleCandidateActivated(transportInfo);
|
2017-06-25 14:25:17 +02:00
|
|
|
|
|
|
|
case JingleS5BTransportInfo.CandidateError.ELEMENT:
|
2017-06-25 15:13:56 +02:00
|
|
|
return handleCandidateError(transportInfo);
|
2017-06-25 14:25:17 +02:00
|
|
|
|
|
|
|
case JingleS5BTransportInfo.ProxyError.ELEMENT:
|
2017-06-25 15:13:56 +02:00
|
|
|
return handleProxyError(transportInfo);
|
2017-06-25 14:25:17 +02:00
|
|
|
|
2017-06-25 15:13:56 +02:00
|
|
|
default:
|
|
|
|
return IQ.createResultIQ(transportInfo);
|
2017-06-25 14:25:17 +02:00
|
|
|
}
|
2017-06-25 15:13:56 +02:00
|
|
|
} else {
|
|
|
|
return jutil.createErrorMalformedRequest(transportInfo);
|
2017-06-25 14:25:17 +02:00
|
|
|
}
|
2017-06-23 22:48:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public JingleTransportManager<JingleS5BTransport> transportManager() {
|
|
|
|
return JingleS5BTransportManager.getInstanceFor(jingleSession.get().getConnection());
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|