001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.bytestreams.socks5;
018
019import java.io.IOException;
020import java.lang.ref.WeakReference;
021import java.net.Socket;
022import java.util.concurrent.TimeoutException;
023
024import org.jivesoftware.smack.SmackException;
025import org.jivesoftware.smack.SmackException.NoResponseException;
026import org.jivesoftware.smack.SmackException.NotConnectedException;
027import org.jivesoftware.smack.SmackException.SmackMessageException;
028import org.jivesoftware.smack.XMPPConnection;
029import org.jivesoftware.smack.XMPPException;
030import org.jivesoftware.smack.XMPPException.XMPPErrorException;
031import org.jivesoftware.smack.packet.IQ;
032
033import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
034import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
035
036import org.jxmpp.jid.Jid;
037
038/**
039 * Implementation of a SOCKS5 client used on the initiators side. This is needed because connecting
040 * to the local SOCKS5 proxy differs form the regular way to connect to a SOCKS5 proxy. Additionally
041 * a remote SOCKS5 proxy has to be activated by the initiator before data can be transferred between
042 * the peers.
043 *
044 * @author Henning Staib
045 */
046public class Socks5ClientForInitiator extends Socks5Client {
047
048    /* the XMPP connection used to communicate with the SOCKS5 proxy */
049    private WeakReference<XMPPConnection> connection;
050
051    /* the session ID used to activate SOCKS5 stream */
052    private String sessionID;
053
054    /* the target JID used to activate SOCKS5 stream */
055    // TODO fullJid?
056    private final Jid target;
057
058    /**
059     * Creates a new SOCKS5 client for the initiators side.
060     *
061     * @param streamHost containing network settings of the SOCKS5 proxy
062     * @param digest identifying the SOCKS5 Bytestream
063     * @param connection the XMPP connection
064     * @param sessionID the session ID of the SOCKS5 Bytestream
065     * @param target the target JID of the SOCKS5 Bytestream
066     */
067    public Socks5ClientForInitiator(StreamHost streamHost, String digest, XMPPConnection connection,
068                    String sessionID, Jid target) {
069        super(streamHost, digest);
070        this.connection = new WeakReference<>(connection);
071        this.sessionID = sessionID;
072        this.target = target;
073    }
074
075    @Override
076    public Socket getSocket(int timeout) throws IOException, InterruptedException,
077                    TimeoutException, XMPPException, SmackMessageException, NotConnectedException, NoResponseException {
078        Socket socket;
079
080        // check if stream host is the local SOCKS5 proxy
081        if (this.streamHost.getJID().equals(this.connection.get().getUser())) {
082            socket = Socks5Proxy.getSocketForDigest(this.digest);
083            if (socket == null) {
084                throw new SmackException.SmackMessageException("target is not connected to SOCKS5 proxy");
085            }
086        }
087        else {
088            socket = super.getSocket(timeout);
089
090            try {
091                activate();
092            }
093            catch (XMPPException e1) {
094                socket.close();
095                throw e1;
096            }
097            catch (NoResponseException e2) {
098                socket.close();
099                throw e2;
100            }
101
102        }
103
104        return socket;
105    }
106
107    /**
108     * Activates the SOCKS5 Bytestream by sending an XMPP SOCKS5 Bytestream activation stanza to the
109     * SOCKS5 proxy.
110     * @throws XMPPErrorException if there was an XMPP error returned.
111     * @throws NoResponseException if there was no response from the remote entity.
112     * @throws NotConnectedException if the XMPP connection is not connected.
113     * @throws InterruptedException if the calling thread was interrupted.
114     */
115    private void activate() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
116        Bytestream activate = createStreamHostActivation();
117        // if activation fails #nextResultOrThrow() throws an exception
118        connection.get().createStanzaCollectorAndSend(activate).nextResultOrThrow();
119    }
120
121    /**
122     * Returns a SOCKS5 Bytestream activation packet.
123     *
124     * @return SOCKS5 Bytestream activation packet
125     */
126    private Bytestream createStreamHostActivation() {
127        Bytestream activate = new Bytestream(this.sessionID);
128        activate.setMode(null);
129        activate.setType(IQ.Type.set);
130        activate.setTo(this.streamHost.getJID());
131
132        activate.setToActivate(this.target);
133
134        return activate;
135    }
136
137}