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.XMPPConnection;
028import org.jivesoftware.smack.XMPPException;
029import org.jivesoftware.smack.XMPPException.XMPPErrorException;
030import org.jivesoftware.smack.packet.IQ;
031
032import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
033import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost;
034
035import org.jxmpp.jid.Jid;
036
037/**
038 * Implementation of a SOCKS5 client used on the initiators side. This is needed because connecting
039 * to the local SOCKS5 proxy differs form the regular way to connect to a SOCKS5 proxy. Additionally
040 * a remote SOCKS5 proxy has to be activated by the initiator before data can be transferred between
041 * the peers.
042 *
043 * @author Henning Staib
044 */
045public class Socks5ClientForInitiator extends Socks5Client {
046
047    /* the XMPP connection used to communicate with the SOCKS5 proxy */
048    private WeakReference<XMPPConnection> connection;
049
050    /* the session ID used to activate SOCKS5 stream */
051    private String sessionID;
052
053    /* the target JID used to activate SOCKS5 stream */
054    // TODO fullJid?
055    private final Jid target;
056
057    /**
058     * Creates a new SOCKS5 client for the initiators side.
059     *
060     * @param streamHost containing network settings of the SOCKS5 proxy
061     * @param digest identifying the SOCKS5 Bytestream
062     * @param connection the XMPP connection
063     * @param sessionID the session ID of the SOCKS5 Bytestream
064     * @param target the target JID of the SOCKS5 Bytestream
065     */
066    public Socks5ClientForInitiator(StreamHost streamHost, String digest, XMPPConnection connection,
067                    String sessionID, Jid target) {
068        super(streamHost, digest);
069        this.connection = new WeakReference<>(connection);
070        this.sessionID = sessionID;
071        this.target = target;
072    }
073
074    @Override
075    public Socket getSocket(int timeout) throws IOException, InterruptedException,
076                    TimeoutException, XMPPException, SmackException {
077        Socket socket;
078
079        // check if stream host is the local SOCKS5 proxy
080        if (this.streamHost.getJID().equals(this.connection.get().getUser())) {
081            Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy();
082            socket = socks5Server.getSocket(this.digest);
083            if (socket == null) {
084                throw new SmackException("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
111     * @throws NoResponseException
112     * @throws NotConnectedException
113     * @throws InterruptedException
114     * @throws SmackException if there was no response from the server.
115     */
116    private void activate() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
117        Bytestream activate = createStreamHostActivation();
118        // if activation fails #nextResultOrThrow() throws an exception
119        connection.get().createStanzaCollectorAndSend(activate).nextResultOrThrow();
120    }
121
122    /**
123     * Returns a SOCKS5 Bytestream activation packet.
124     *
125     * @return SOCKS5 Bytestream activation packet
126     */
127    private Bytestream createStreamHostActivation() {
128        Bytestream activate = new Bytestream(this.sessionID);
129        activate.setMode(null);
130        activate.setType(IQ.Type.set);
131        activate.setTo(this.streamHost.getJID());
132
133        activate.setToActivate(this.target);
134
135        return activate;
136    }
137
138}