1/**
2 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 *     http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14package org.jivesoftware.smackx.filetransfer;
15
16import java.io.IOException;
17import java.io.InputStream;
18import java.io.OutputStream;
19import java.io.PushbackInputStream;
20
21import org.jivesoftware.smack.Connection;
22import org.jivesoftware.smack.XMPPException;
23import org.jivesoftware.smack.filter.AndFilter;
24import org.jivesoftware.smack.filter.FromMatchesFilter;
25import org.jivesoftware.smack.filter.PacketFilter;
26import org.jivesoftware.smack.filter.PacketTypeFilter;
27import org.jivesoftware.smack.packet.IQ;
28import org.jivesoftware.smack.packet.Packet;
29import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager;
30import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest;
31import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession;
32import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream;
33import org.jivesoftware.smackx.packet.StreamInitiation;
34
35/**
36 * Negotiates a SOCKS5 Bytestream to be used for file transfers. The implementation is based on the
37 * {@link Socks5BytestreamManager} and the {@link Socks5BytestreamRequest}.
38 *
39 * @author Henning Staib
40 * @see <a href="http://xmpp.org/extensions/xep-0065.html">XEP-0065: SOCKS5 Bytestreams</a>
41 */
42public class Socks5TransferNegotiator extends StreamNegotiator {
43
44    private Connection connection;
45
46    private Socks5BytestreamManager manager;
47
48    Socks5TransferNegotiator(Connection connection) {
49        this.connection = connection;
50        this.manager = Socks5BytestreamManager.getBytestreamManager(this.connection);
51    }
52
53    @Override
54    public OutputStream createOutgoingStream(String streamID, String initiator, String target)
55                    throws XMPPException {
56        try {
57            return this.manager.establishSession(target, streamID).getOutputStream();
58        }
59        catch (IOException e) {
60            throw new XMPPException("error establishing SOCKS5 Bytestream", e);
61        }
62        catch (InterruptedException e) {
63            throw new XMPPException("error establishing SOCKS5 Bytestream", e);
64        }
65    }
66
67    @Override
68    public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPException,
69                    InterruptedException {
70        /*
71         * SOCKS5 initiation listener must ignore next SOCKS5 Bytestream request with given session
72         * ID
73         */
74        this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID());
75
76        Packet streamInitiation = initiateIncomingStream(this.connection, initiation);
77        return negotiateIncomingStream(streamInitiation);
78    }
79
80    @Override
81    public PacketFilter getInitiationPacketFilter(final String from, String streamID) {
82        /*
83         * this method is always called prior to #negotiateIncomingStream() so the SOCKS5
84         * InitiationListener must ignore the next SOCKS5 Bytestream request with the given session
85         * ID
86         */
87        this.manager.ignoreBytestreamRequestOnce(streamID);
88
89        return new AndFilter(new FromMatchesFilter(from), new BytestreamSIDFilter(streamID));
90    }
91
92    @Override
93    public String[] getNamespaces() {
94        return new String[] { Socks5BytestreamManager.NAMESPACE };
95    }
96
97    @Override
98    InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException,
99                    InterruptedException {
100        // build SOCKS5 Bytestream request
101        Socks5BytestreamRequest request = new ByteStreamRequest(this.manager,
102                        (Bytestream) streamInitiation);
103
104        // always accept the request
105        Socks5BytestreamSession session = request.accept();
106
107        // test input stream
108        try {
109            PushbackInputStream stream = new PushbackInputStream(session.getInputStream());
110            int firstByte = stream.read();
111            stream.unread(firstByte);
112            return stream;
113        }
114        catch (IOException e) {
115            throw new XMPPException("Error establishing input stream", e);
116        }
117    }
118
119    @Override
120    public void cleanup() {
121        /* do nothing */
122    }
123
124    /**
125     * This PacketFilter accepts an incoming SOCKS5 Bytestream request with a specified session ID.
126     */
127    private static class BytestreamSIDFilter extends PacketTypeFilter {
128
129        private String sessionID;
130
131        public BytestreamSIDFilter(String sessionID) {
132            super(Bytestream.class);
133            if (sessionID == null) {
134                throw new IllegalArgumentException("StreamID cannot be null");
135            }
136            this.sessionID = sessionID;
137        }
138
139        @Override
140        public boolean accept(Packet packet) {
141            if (super.accept(packet)) {
142                Bytestream bytestream = (Bytestream) packet;
143
144                // packet must by of type SET and contains the given session ID
145                return this.sessionID.equals(bytestream.getSessionID())
146                                && IQ.Type.SET.equals(bytestream.getType());
147            }
148            return false;
149        }
150
151    }
152
153    /**
154     * Derive from Socks5BytestreamRequest to access protected constructor.
155     */
156    private static class ByteStreamRequest extends Socks5BytestreamRequest {
157
158        private ByteStreamRequest(Socks5BytestreamManager manager, Bytestream byteStreamRequest) {
159            super(manager, byteStreamRequest);
160        }
161
162    }
163
164}
165