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.packet; 018 019import java.net.InetAddress; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.List; 023 024import org.jivesoftware.smack.packet.ExtensionElement; 025import org.jivesoftware.smack.packet.IQ; 026import org.jivesoftware.smack.util.InternetAddress; 027import org.jivesoftware.smack.util.Objects; 028import org.jivesoftware.smack.util.XmlStringBuilder; 029 030import org.jxmpp.jid.Jid; 031 032/** 033 * A stanza representing part of a SOCKS5 Bytestream negotiation. 034 * 035 * @author Alexander Wenckus 036 */ 037public class Bytestream extends IQ { 038 039 public static final String ELEMENT = QUERY_ELEMENT; 040 041 /** 042 * The XMPP namespace of the SOCKS5 Bytestream. 043 */ 044 public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams"; 045 046 private String sessionID; 047 048 private Mode mode = Mode.tcp; 049 050 private final List<StreamHost> streamHosts = new ArrayList<>(); 051 052 private StreamHostUsed usedHost; 053 054 private Activate toActivate; 055 056 /** 057 * The default constructor. 058 */ 059 public Bytestream() { 060 super(ELEMENT, NAMESPACE); 061 } 062 063 /** 064 * A constructor where the session ID can be specified. 065 * 066 * @param SID The session ID related to the negotiation. 067 * @see #setSessionID(String) 068 */ 069 public Bytestream(final String SID) { 070 this(); 071 setSessionID(SID); 072 } 073 074 /** 075 * Set the session ID related to the bytestream. The session ID is a unique identifier used to 076 * differentiate between stream negotiations. 077 * 078 * @param sessionID the unique session ID that identifies the transfer. 079 */ 080 public void setSessionID(final String sessionID) { 081 this.sessionID = sessionID; 082 } 083 084 /** 085 * Returns the session ID related to the bytestream negotiation. 086 * 087 * @return Returns the session ID related to the bytestream negotiation. 088 * @see #setSessionID(String) 089 */ 090 public String getSessionID() { 091 return sessionID; 092 } 093 094 /** 095 * Set the transport mode. This should be put in the initiation of the interaction. 096 * 097 * @param mode the transport mode, either UDP or TCP 098 * @see Mode 099 */ 100 public void setMode(final Mode mode) { 101 this.mode = mode; 102 } 103 104 /** 105 * Returns the transport mode. 106 * 107 * @return Returns the transport mode. 108 * @see #setMode(Mode) 109 */ 110 public Mode getMode() { 111 return mode; 112 } 113 114 /** 115 * Adds a potential stream host that the remote user can connect to to receive the file. 116 * 117 * @param JID The JID of the stream host. 118 * @param address The internet address of the stream host. 119 * @return The added stream host. 120 */ 121 public StreamHost addStreamHost(final Jid JID, String address) { 122 return addStreamHost(JID, address, 0); 123 } 124 125 /** 126 * Adds a potential stream host that the remote user can connect to to receive the file. 127 * 128 * @param JID The JID of the stream host. 129 * @param address The internet address of the stream host. 130 * @param port The port on which the remote host is seeking connections. 131 * @return The added stream host. 132 */ 133 public StreamHost addStreamHost(final Jid JID, String address, final int port) { 134 StreamHost host = new StreamHost(JID, address, port); 135 addStreamHost(host); 136 137 return host; 138 } 139 140 /** 141 * Adds a potential stream host that the remote user can transfer the file through. 142 * 143 * @param host The potential stream host. 144 */ 145 public void addStreamHost(final StreamHost host) { 146 streamHosts.add(host); 147 } 148 149 /** 150 * Returns the list of stream hosts contained in the packet. 151 * 152 * @return Returns the list of stream hosts contained in the packet. 153 */ 154 public List<StreamHost> getStreamHosts() { 155 return Collections.unmodifiableList(streamHosts); 156 } 157 158 /** 159 * Returns the stream host related to the given JID, or null if there is none. 160 * 161 * @param JID The JID of the desired stream host. 162 * @return Returns the stream host related to the given JID, or null if there is none. 163 */ 164 public StreamHost getStreamHost(final Jid JID) { 165 if (JID == null) { 166 return null; 167 } 168 for (StreamHost host : streamHosts) { 169 if (host.getJID().equals(JID)) { 170 return host; 171 } 172 } 173 174 return null; 175 } 176 177 /** 178 * Returns the count of stream hosts contained in this packet. 179 * 180 * @return Returns the count of stream hosts contained in this packet. 181 */ 182 public int countStreamHosts() { 183 return streamHosts.size(); 184 } 185 186 /** 187 * Upon connecting to the stream host the target of the stream replies to the initiator with the 188 * JID of the SOCKS5 host that they used. 189 * 190 * @param JID The JID of the used host. 191 */ 192 public void setUsedHost(final Jid JID) { 193 this.usedHost = new StreamHostUsed(JID); 194 } 195 196 /** 197 * Returns the SOCKS5 host connected to by the remote user. 198 * 199 * @return Returns the SOCKS5 host connected to by the remote user. 200 */ 201 public StreamHostUsed getUsedHost() { 202 return usedHost; 203 } 204 205 /** 206 * Returns the activate element of the stanza sent to the proxy host to verify the identity of 207 * the initiator and match them to the appropriate stream. 208 * 209 * @return Returns the activate element of the stanza sent to the proxy host to verify the 210 * identity of the initiator and match them to the appropriate stream. 211 */ 212 public Activate getToActivate() { 213 return toActivate; 214 } 215 216 /** 217 * Upon the response from the target of the used host the activate stanza is sent to the SOCKS5 218 * proxy. The proxy will activate the stream or return an error after verifying the identity of 219 * the initiator, using the activate packet. 220 * 221 * @param targetID The JID of the target of the file transfer. 222 */ 223 public void setToActivate(final Jid targetID) { 224 this.toActivate = new Activate(targetID); 225 } 226 227 @Override 228 protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { 229 switch (getType()) { 230 case set: 231 xml.optAttribute("sid", getSessionID()); 232 xml.optAttribute("mode", getMode()); 233 xml.rightAngleBracket(); 234 if (getToActivate() == null) { 235 for (StreamHost streamHost : getStreamHosts()) { 236 xml.append(streamHost.toXML()); 237 } 238 } 239 else { 240 xml.append(getToActivate().toXML()); 241 } 242 break; 243 case result: 244 xml.rightAngleBracket(); 245 xml.optAppend(getUsedHost()); 246 // TODO Bytestream can include either used host *or* streamHosts. Never both. This should be ensured by the 247 // constructions mechanisms of Bytestream 248 // A result from the server can also contain stream hosts 249 for (StreamHost host : streamHosts) { 250 xml.append(host.toXML()); 251 } 252 break; 253 case get: 254 xml.setEmptyElement(); 255 break; 256 default: 257 throw new IllegalStateException(); 258 } 259 260 return xml; 261 } 262 263 private abstract static class BytestreamExtensionElement implements ExtensionElement { 264 @Override 265 public final String getNamespace() { 266 return NAMESPACE; 267 } 268 } 269 270 /** 271 * Stanza extension that represents a potential SOCKS5 proxy for the file transfer. Stream hosts 272 * are forwarded to the target of the file transfer who then chooses and connects to one. 273 * 274 * @author Alexander Wenckus 275 */ 276 public static class StreamHost extends BytestreamExtensionElement { 277 278 public static String ELEMENTNAME = "streamhost"; 279 280 private final Jid jid; 281 282 private final InternetAddress address; 283 284 private final int port; 285 286 public StreamHost(Jid jid, String address) { 287 this(jid, address, 0); 288 } 289 290 /** 291 * Default constructor. 292 * 293 * @param jid The JID of the stream host. 294 * @param address The internet address of the stream host. 295 * @param port port of the stream host. 296 */ 297 public StreamHost(final Jid jid, final String address, int port) { 298 this(jid, InternetAddress.from(address), port); 299 } 300 301 public StreamHost(Jid jid, InetAddress address, int port) { 302 this(jid, InternetAddress.from(address), port); 303 } 304 305 /** 306 * Stream Host constructor. 307 * 308 * @param jid The JID of the stream host. 309 * @param address The internet address of the stream host. 310 * @param port port of the stream host. 311 */ 312 public StreamHost(Jid jid, InternetAddress address, int port) { 313 this.jid = Objects.requireNonNull(jid, "StreamHost JID must not be null"); 314 this.address = Objects.requireNonNull(address); 315 this.port = port; 316 } 317 318 /** 319 * Returns the JID of the stream host. 320 * 321 * @return Returns the JID of the stream host. 322 */ 323 public Jid getJID() { 324 return jid; 325 } 326 327 /** 328 * Returns the internet address of the stream host. 329 * 330 * @return Returns the internet address of the stream host. 331 */ 332 public InternetAddress getAddress() { 333 return address; 334 } 335 336 /** 337 * Returns the port on which the potential stream host would accept the connection. 338 * 339 * @return Returns the port on which the potential stream host would accept the connection. 340 */ 341 public int getPort() { 342 return port; 343 } 344 345 @Override 346 public String getElementName() { 347 return ELEMENTNAME; 348 } 349 350 @Override 351 public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 352 XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); 353 xml.attribute("jid", getJID()); 354 xml.attribute("host", address); 355 if (getPort() != 0) { 356 xml.attribute("port", Integer.toString(getPort())); 357 } else { 358 xml.attribute("zeroconf", "_jabber.bytestreams"); 359 } 360 xml.closeEmptyElement(); 361 return xml; 362 } 363 364 @Override 365 public String toString() { 366 return "SOCKS5 Stream Host: " + jid + "[" + address + ":" + port + "]"; 367 } 368 } 369 370 /** 371 * After selected a SOCKS5 stream host and successfully connecting, the target of the file 372 * transfer returns a byte stream stanza with the stream host used extension. 373 * 374 * @author Alexander Wenckus 375 */ 376 public static class StreamHostUsed extends BytestreamExtensionElement { 377 378 public static String ELEMENTNAME = "streamhost-used"; 379 380 private final Jid jid; 381 382 /** 383 * Default constructor. 384 * 385 * @param jid The JID of the selected stream host. 386 */ 387 public StreamHostUsed(final Jid jid) { 388 this.jid = jid; 389 } 390 391 /** 392 * Returns the JID of the selected stream host. 393 * 394 * @return Returns the JID of the selected stream host. 395 */ 396 public Jid getJID() { 397 return jid; 398 } 399 400 @Override 401 public String getElementName() { 402 return ELEMENTNAME; 403 } 404 405 @Override 406 public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 407 XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); 408 xml.attribute("jid", getJID()); 409 xml.closeEmptyElement(); 410 return xml; 411 } 412 } 413 414 /** 415 * The stanza sent by the stream initiator to the stream proxy to activate the connection. 416 * 417 * @author Alexander Wenckus 418 */ 419 public static class Activate extends BytestreamExtensionElement { 420 421 public static String ELEMENTNAME = "activate"; 422 423 private final Jid target; 424 425 /** 426 * Default constructor specifying the target of the stream. 427 * 428 * @param target The target of the stream. 429 */ 430 public Activate(final Jid target) { 431 this.target = target; 432 } 433 434 /** 435 * Returns the target of the activation. 436 * 437 * @return Returns the target of the activation. 438 */ 439 public Jid getTarget() { 440 return target; 441 } 442 443 @Override 444 public String getElementName() { 445 return ELEMENTNAME; 446 } 447 448 @Override 449 public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { 450 XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace); 451 xml.rightAngleBracket(); 452 xml.escape(getTarget()); 453 xml.closeElement(this); 454 return xml; 455 } 456 457 } 458 459 /** 460 * The stream can be either a TCP stream or a UDP stream. 461 * 462 * @author Alexander Wenckus 463 */ 464 public enum Mode { 465 466 /** 467 * A TCP based stream. 468 */ 469 tcp, 470 471 /** 472 * A UDP based stream. 473 */ 474 udp; 475 476 public static Mode fromName(String name) { 477 Mode mode; 478 try { 479 mode = Mode.valueOf(name); 480 } 481 catch (Exception ex) { 482 mode = tcp; 483 } 484 485 return mode; 486 } 487 } 488}