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 */ 14 package org.jivesoftware.smackx.filetransfer; 15 16 import java.io.IOException; 17 import java.io.InputStream; 18 import java.io.OutputStream; 19 import java.io.PushbackInputStream; 20 21 import org.jivesoftware.smack.Connection; 22 import org.jivesoftware.smack.XMPPException; 23 import org.jivesoftware.smack.filter.AndFilter; 24 import org.jivesoftware.smack.filter.FromMatchesFilter; 25 import org.jivesoftware.smack.filter.PacketFilter; 26 import org.jivesoftware.smack.filter.PacketTypeFilter; 27 import org.jivesoftware.smack.packet.IQ; 28 import org.jivesoftware.smack.packet.Packet; 29 import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; 30 import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; 31 import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; 32 import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; 33 import 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 */ 42 public 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