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.bytestreams.socks5.packet; 15 16 import java.util.ArrayList; 17 import java.util.Collection; 18 import java.util.Collections; 19 import java.util.List; 20 21 import org.jivesoftware.smack.packet.IQ; 22 import org.jivesoftware.smack.packet.PacketExtension; 23 24 /** 25 * A packet representing part of a SOCKS5 Bytestream negotiation. 26 * 27 * @author Alexander Wenckus 28 */ 29 public class Bytestream extends IQ { 30 31 private String sessionID; 32 33 private Mode mode = Mode.tcp; 34 35 private final List<StreamHost> streamHosts = new ArrayList<StreamHost>(); 36 37 private StreamHostUsed usedHost; 38 39 private Activate toActivate; 40 41 /** 42 * The default constructor 43 */ Bytestream()44 public Bytestream() { 45 super(); 46 } 47 48 /** 49 * A constructor where the session ID can be specified. 50 * 51 * @param SID The session ID related to the negotiation. 52 * @see #setSessionID(String) 53 */ Bytestream(final String SID)54 public Bytestream(final String SID) { 55 super(); 56 setSessionID(SID); 57 } 58 59 /** 60 * Set the session ID related to the bytestream. The session ID is a unique identifier used to 61 * differentiate between stream negotiations. 62 * 63 * @param sessionID the unique session ID that identifies the transfer. 64 */ setSessionID(final String sessionID)65 public void setSessionID(final String sessionID) { 66 this.sessionID = sessionID; 67 } 68 69 /** 70 * Returns the session ID related to the bytestream negotiation. 71 * 72 * @return Returns the session ID related to the bytestream negotiation. 73 * @see #setSessionID(String) 74 */ getSessionID()75 public String getSessionID() { 76 return sessionID; 77 } 78 79 /** 80 * Set the transport mode. This should be put in the initiation of the interaction. 81 * 82 * @param mode the transport mode, either UDP or TCP 83 * @see Mode 84 */ setMode(final Mode mode)85 public void setMode(final Mode mode) { 86 this.mode = mode; 87 } 88 89 /** 90 * Returns the transport mode. 91 * 92 * @return Returns the transport mode. 93 * @see #setMode(Mode) 94 */ getMode()95 public Mode getMode() { 96 return mode; 97 } 98 99 /** 100 * Adds a potential stream host that the remote user can connect to to receive the file. 101 * 102 * @param JID The JID of the stream host. 103 * @param address The internet address of the stream host. 104 * @return The added stream host. 105 */ addStreamHost(final String JID, final String address)106 public StreamHost addStreamHost(final String JID, final String address) { 107 return addStreamHost(JID, address, 0); 108 } 109 110 /** 111 * Adds a potential stream host that the remote user can connect to to receive the file. 112 * 113 * @param JID The JID of the stream host. 114 * @param address The internet address of the stream host. 115 * @param port The port on which the remote host is seeking connections. 116 * @return The added stream host. 117 */ addStreamHost(final String JID, final String address, final int port)118 public StreamHost addStreamHost(final String JID, final String address, final int port) { 119 StreamHost host = new StreamHost(JID, address); 120 host.setPort(port); 121 addStreamHost(host); 122 123 return host; 124 } 125 126 /** 127 * Adds a potential stream host that the remote user can transfer the file through. 128 * 129 * @param host The potential stream host. 130 */ addStreamHost(final StreamHost host)131 public void addStreamHost(final StreamHost host) { 132 streamHosts.add(host); 133 } 134 135 /** 136 * Returns the list of stream hosts contained in the packet. 137 * 138 * @return Returns the list of stream hosts contained in the packet. 139 */ getStreamHosts()140 public Collection<StreamHost> getStreamHosts() { 141 return Collections.unmodifiableCollection(streamHosts); 142 } 143 144 /** 145 * Returns the stream host related to the given JID, or null if there is none. 146 * 147 * @param JID The JID of the desired stream host. 148 * @return Returns the stream host related to the given JID, or null if there is none. 149 */ getStreamHost(final String JID)150 public StreamHost getStreamHost(final String JID) { 151 if (JID == null) { 152 return null; 153 } 154 for (StreamHost host : streamHosts) { 155 if (host.getJID().equals(JID)) { 156 return host; 157 } 158 } 159 160 return null; 161 } 162 163 /** 164 * Returns the count of stream hosts contained in this packet. 165 * 166 * @return Returns the count of stream hosts contained in this packet. 167 */ countStreamHosts()168 public int countStreamHosts() { 169 return streamHosts.size(); 170 } 171 172 /** 173 * Upon connecting to the stream host the target of the stream replies to the initiator with the 174 * JID of the SOCKS5 host that they used. 175 * 176 * @param JID The JID of the used host. 177 */ setUsedHost(final String JID)178 public void setUsedHost(final String JID) { 179 this.usedHost = new StreamHostUsed(JID); 180 } 181 182 /** 183 * Returns the SOCKS5 host connected to by the remote user. 184 * 185 * @return Returns the SOCKS5 host connected to by the remote user. 186 */ getUsedHost()187 public StreamHostUsed getUsedHost() { 188 return usedHost; 189 } 190 191 /** 192 * Returns the activate element of the packet sent to the proxy host to verify the identity of 193 * the initiator and match them to the appropriate stream. 194 * 195 * @return Returns the activate element of the packet sent to the proxy host to verify the 196 * identity of the initiator and match them to the appropriate stream. 197 */ getToActivate()198 public Activate getToActivate() { 199 return toActivate; 200 } 201 202 /** 203 * Upon the response from the target of the used host the activate packet is sent to the SOCKS5 204 * proxy. The proxy will activate the stream or return an error after verifying the identity of 205 * the initiator, using the activate packet. 206 * 207 * @param targetID The JID of the target of the file transfer. 208 */ setToActivate(final String targetID)209 public void setToActivate(final String targetID) { 210 this.toActivate = new Activate(targetID); 211 } 212 getChildElementXML()213 public String getChildElementXML() { 214 StringBuilder buf = new StringBuilder(); 215 216 buf.append("<query xmlns=\"http://jabber.org/protocol/bytestreams\""); 217 if (this.getType().equals(IQ.Type.SET)) { 218 if (getSessionID() != null) { 219 buf.append(" sid=\"").append(getSessionID()).append("\""); 220 } 221 if (getMode() != null) { 222 buf.append(" mode = \"").append(getMode()).append("\""); 223 } 224 buf.append(">"); 225 if (getToActivate() == null) { 226 for (StreamHost streamHost : getStreamHosts()) { 227 buf.append(streamHost.toXML()); 228 } 229 } 230 else { 231 buf.append(getToActivate().toXML()); 232 } 233 } 234 else if (this.getType().equals(IQ.Type.RESULT)) { 235 buf.append(">"); 236 if (getUsedHost() != null) { 237 buf.append(getUsedHost().toXML()); 238 } 239 // A result from the server can also contain stream hosts 240 else if (countStreamHosts() > 0) { 241 for (StreamHost host : streamHosts) { 242 buf.append(host.toXML()); 243 } 244 } 245 } 246 else if (this.getType().equals(IQ.Type.GET)) { 247 return buf.append("/>").toString(); 248 } 249 else { 250 return null; 251 } 252 buf.append("</query>"); 253 254 return buf.toString(); 255 } 256 257 /** 258 * Packet extension that represents a potential SOCKS5 proxy for the file transfer. Stream hosts 259 * are forwarded to the target of the file transfer who then chooses and connects to one. 260 * 261 * @author Alexander Wenckus 262 */ 263 public static class StreamHost implements PacketExtension { 264 265 public static String NAMESPACE = ""; 266 267 public static String ELEMENTNAME = "streamhost"; 268 269 private final String JID; 270 271 private final String addy; 272 273 private int port = 0; 274 275 /** 276 * Default constructor. 277 * 278 * @param JID The JID of the stream host. 279 * @param address The internet address of the stream host. 280 */ StreamHost(final String JID, final String address)281 public StreamHost(final String JID, final String address) { 282 this.JID = JID; 283 this.addy = address; 284 } 285 286 /** 287 * Returns the JID of the stream host. 288 * 289 * @return Returns the JID of the stream host. 290 */ getJID()291 public String getJID() { 292 return JID; 293 } 294 295 /** 296 * Returns the internet address of the stream host. 297 * 298 * @return Returns the internet address of the stream host. 299 */ getAddress()300 public String getAddress() { 301 return addy; 302 } 303 304 /** 305 * Sets the port of the stream host. 306 * 307 * @param port The port on which the potential stream host would accept the connection. 308 */ setPort(final int port)309 public void setPort(final int port) { 310 this.port = port; 311 } 312 313 /** 314 * Returns the port on which the potential stream host would accept the connection. 315 * 316 * @return Returns the port on which the potential stream host would accept the connection. 317 */ getPort()318 public int getPort() { 319 return port; 320 } 321 getNamespace()322 public String getNamespace() { 323 return NAMESPACE; 324 } 325 getElementName()326 public String getElementName() { 327 return ELEMENTNAME; 328 } 329 toXML()330 public String toXML() { 331 StringBuilder buf = new StringBuilder(); 332 333 buf.append("<").append(getElementName()).append(" "); 334 buf.append("jid=\"").append(getJID()).append("\" "); 335 buf.append("host=\"").append(getAddress()).append("\" "); 336 if (getPort() != 0) { 337 buf.append("port=\"").append(getPort()).append("\""); 338 } 339 else { 340 buf.append("zeroconf=\"_jabber.bytestreams\""); 341 } 342 buf.append("/>"); 343 344 return buf.toString(); 345 } 346 } 347 348 /** 349 * After selected a SOCKS5 stream host and successfully connecting, the target of the file 350 * transfer returns a byte stream packet with the stream host used extension. 351 * 352 * @author Alexander Wenckus 353 */ 354 public static class StreamHostUsed implements PacketExtension { 355 356 public String NAMESPACE = ""; 357 358 public static String ELEMENTNAME = "streamhost-used"; 359 360 private final String JID; 361 362 /** 363 * Default constructor. 364 * 365 * @param JID The JID of the selected stream host. 366 */ StreamHostUsed(final String JID)367 public StreamHostUsed(final String JID) { 368 this.JID = JID; 369 } 370 371 /** 372 * Returns the JID of the selected stream host. 373 * 374 * @return Returns the JID of the selected stream host. 375 */ getJID()376 public String getJID() { 377 return JID; 378 } 379 getNamespace()380 public String getNamespace() { 381 return NAMESPACE; 382 } 383 getElementName()384 public String getElementName() { 385 return ELEMENTNAME; 386 } 387 toXML()388 public String toXML() { 389 StringBuilder buf = new StringBuilder(); 390 buf.append("<").append(getElementName()).append(" "); 391 buf.append("jid=\"").append(getJID()).append("\" "); 392 buf.append("/>"); 393 return buf.toString(); 394 } 395 } 396 397 /** 398 * The packet sent by the stream initiator to the stream proxy to activate the connection. 399 * 400 * @author Alexander Wenckus 401 */ 402 public static class Activate implements PacketExtension { 403 404 public String NAMESPACE = ""; 405 406 public static String ELEMENTNAME = "activate"; 407 408 private final String target; 409 410 /** 411 * Default constructor specifying the target of the stream. 412 * 413 * @param target The target of the stream. 414 */ Activate(final String target)415 public Activate(final String target) { 416 this.target = target; 417 } 418 419 /** 420 * Returns the target of the activation. 421 * 422 * @return Returns the target of the activation. 423 */ getTarget()424 public String getTarget() { 425 return target; 426 } 427 getNamespace()428 public String getNamespace() { 429 return NAMESPACE; 430 } 431 getElementName()432 public String getElementName() { 433 return ELEMENTNAME; 434 } 435 toXML()436 public String toXML() { 437 StringBuilder buf = new StringBuilder(); 438 buf.append("<").append(getElementName()).append(">"); 439 buf.append(getTarget()); 440 buf.append("</").append(getElementName()).append(">"); 441 return buf.toString(); 442 } 443 } 444 445 /** 446 * The stream can be either a TCP stream or a UDP stream. 447 * 448 * @author Alexander Wenckus 449 */ 450 public enum Mode { 451 452 /** 453 * A TCP based stream. 454 */ 455 tcp, 456 457 /** 458 * A UDP based stream. 459 */ 460 udp; 461 fromName(String name)462 public static Mode fromName(String name) { 463 Mode mode; 464 try { 465 mode = Mode.valueOf(name); 466 } 467 catch (Exception ex) { 468 mode = tcp; 469 } 470 471 return mode; 472 } 473 } 474 } 475