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.filetransfer; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.io.PushbackInputStream; 023 024import org.jivesoftware.smack.SmackException; 025import org.jivesoftware.smack.SmackException.NoResponseException; 026import org.jivesoftware.smack.XMPPConnection; 027import org.jivesoftware.smack.XMPPException; 028import org.jivesoftware.smack.XMPPException.XMPPErrorException; 029import org.jivesoftware.smack.filter.AndFilter; 030import org.jivesoftware.smack.filter.FromMatchesFilter; 031import org.jivesoftware.smack.filter.PacketFilter; 032import org.jivesoftware.smack.filter.PacketTypeFilter; 033import org.jivesoftware.smack.packet.IQ; 034import org.jivesoftware.smack.packet.Packet; 035import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; 036import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; 037import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; 038import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; 039import org.jivesoftware.smackx.si.packet.StreamInitiation; 040 041/** 042 * Negotiates a SOCKS5 Bytestream to be used for file transfers. The implementation is based on the 043 * {@link Socks5BytestreamManager} and the {@link Socks5BytestreamRequest}. 044 * 045 * @author Henning Staib 046 * @see <a href="http://xmpp.org/extensions/xep-0065.html">XEP-0065: SOCKS5 Bytestreams</a> 047 */ 048public class Socks5TransferNegotiator extends StreamNegotiator { 049 050 private XMPPConnection connection; 051 052 private Socks5BytestreamManager manager; 053 054 Socks5TransferNegotiator(XMPPConnection connection) { 055 this.connection = connection; 056 this.manager = Socks5BytestreamManager.getBytestreamManager(this.connection); 057 } 058 059 @Override 060 public OutputStream createOutgoingStream(String streamID, String initiator, String target) throws NoResponseException, SmackException, XMPPException 061 { 062 try { 063 return this.manager.establishSession(target, streamID).getOutputStream(); 064 } 065 catch (IOException e) { 066 throw new SmackException("error establishing SOCKS5 Bytestream", e); 067 } 068 catch (InterruptedException e) { 069 throw new SmackException("error establishing SOCKS5 Bytestream", e); 070 } 071 } 072 073 @Override 074 public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPErrorException, 075 InterruptedException, SmackException { 076 /* 077 * SOCKS5 initiation listener must ignore next SOCKS5 Bytestream request with given session 078 * ID 079 */ 080 this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID()); 081 082 Packet streamInitiation = initiateIncomingStream(this.connection, initiation); 083 return negotiateIncomingStream(streamInitiation); 084 } 085 086 @Override 087 public PacketFilter getInitiationPacketFilter(final String from, String streamID) { 088 /* 089 * this method is always called prior to #negotiateIncomingStream() so the SOCKS5 090 * InitiationListener must ignore the next SOCKS5 Bytestream request with the given session 091 * ID 092 */ 093 this.manager.ignoreBytestreamRequestOnce(streamID); 094 095 return new AndFilter(FromMatchesFilter.create(from), new BytestreamSIDFilter(streamID)); 096 } 097 098 @Override 099 public String[] getNamespaces() { 100 return new String[] { Socks5BytestreamManager.NAMESPACE }; 101 } 102 103 @Override 104 InputStream negotiateIncomingStream(Packet streamInitiation) throws InterruptedException, 105 SmackException, XMPPErrorException { 106 // build SOCKS5 Bytestream request 107 Socks5BytestreamRequest request = new ByteStreamRequest(this.manager, 108 (Bytestream) streamInitiation); 109 110 // always accept the request 111 Socks5BytestreamSession session = request.accept(); 112 113 // test input stream 114 try { 115 PushbackInputStream stream = new PushbackInputStream(session.getInputStream()); 116 int firstByte = stream.read(); 117 stream.unread(firstByte); 118 return stream; 119 } 120 catch (IOException e) { 121 throw new SmackException("Error establishing input stream", e); 122 } 123 } 124 125 @Override 126 public void cleanup() { 127 /* do nothing */ 128 } 129 130 /** 131 * This PacketFilter accepts an incoming SOCKS5 Bytestream request with a specified session ID. 132 */ 133 private static class BytestreamSIDFilter extends PacketTypeFilter { 134 135 private String sessionID; 136 137 public BytestreamSIDFilter(String sessionID) { 138 super(Bytestream.class); 139 if (sessionID == null) { 140 throw new IllegalArgumentException("StreamID cannot be null"); 141 } 142 this.sessionID = sessionID; 143 } 144 145 @Override 146 public boolean accept(Packet packet) { 147 if (super.accept(packet)) { 148 Bytestream bytestream = (Bytestream) packet; 149 150 // packet must by of type SET and contains the given session ID 151 return this.sessionID.equals(bytestream.getSessionID()) 152 && IQ.Type.SET.equals(bytestream.getType()); 153 } 154 return false; 155 } 156 157 } 158 159 /** 160 * Derive from Socks5BytestreamRequest to access protected constructor. 161 */ 162 private static class ByteStreamRequest extends Socks5BytestreamRequest { 163 164 private ByteStreamRequest(Socks5BytestreamManager manager, Bytestream byteStreamRequest) { 165 super(manager, byteStreamRequest); 166 } 167 168 } 169 170}