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