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