PageRenderTime 54ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/src/java/org/jivesoftware/openfire/net/SocketConnection.java

https://gitlab.com/javajamesb08/Openfire
Java | 699 lines | 450 code | 72 blank | 177 comment | 56 complexity | 8617a75d366962192d0e3baf9f7ee3f6 MD5 | raw file
Possible License(s): MIT, GPL-2.0
  1. /**
  2. * $RCSfile$
  3. * $Revision: 3187 $
  4. * $Date: 2005-12-11 13:34:34 -0300 (Sun, 11 Dec 2005) $
  5. *
  6. * Copyright (C) 2005-2008 Jive Software. All rights reserved.
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. package org.jivesoftware.openfire.net;
  21. import java.io.BufferedWriter;
  22. import java.io.IOException;
  23. import java.io.OutputStreamWriter;
  24. import java.io.Writer;
  25. import java.net.Socket;
  26. import java.net.UnknownHostException;
  27. import java.nio.channels.Channels;
  28. import java.security.cert.Certificate;
  29. import java.util.Collection;
  30. import java.util.Date;
  31. import java.util.HashMap;
  32. import java.util.Map;
  33. import java.util.concurrent.ConcurrentHashMap;
  34. import java.util.concurrent.atomic.AtomicBoolean;
  35. import javax.net.ssl.SSLPeerUnverifiedException;
  36. import org.jivesoftware.openfire.Connection;
  37. import org.jivesoftware.openfire.ConnectionCloseListener;
  38. import org.jivesoftware.openfire.PacketDeliverer;
  39. import org.jivesoftware.openfire.PacketException;
  40. import org.jivesoftware.openfire.auth.UnauthorizedException;
  41. import org.jivesoftware.openfire.session.IncomingServerSession;
  42. import org.jivesoftware.openfire.session.LocalSession;
  43. import org.jivesoftware.openfire.session.Session;
  44. import org.jivesoftware.util.JiveGlobals;
  45. import org.jivesoftware.util.LocaleUtils;
  46. import org.slf4j.Logger;
  47. import org.slf4j.LoggerFactory;
  48. import org.xmpp.packet.Packet;
  49. import com.jcraft.jzlib.JZlib;
  50. import com.jcraft.jzlib.ZOutputStream;
  51. /**
  52. * An object to track the state of a XMPP client-server session.
  53. * Currently this class contains the socket channel connecting the
  54. * client and server.
  55. *
  56. * @author Iain Shigeoka
  57. */
  58. public class SocketConnection implements Connection {
  59. private static final Logger Log = LoggerFactory.getLogger(SocketConnection.class);
  60. /**
  61. * The utf-8 charset for decoding and encoding XMPP packet streams.
  62. */
  63. public static final String CHARSET = "UTF-8";
  64. private static Map<SocketConnection, String> instances =
  65. new ConcurrentHashMap<SocketConnection, String>();
  66. /**
  67. * Milliseconds a connection has to be idle to be closed. Timeout is disabled by default. It's
  68. * up to the connection's owner to configure the timeout value. Sending stanzas to the client
  69. * is not considered as activity. We are only considering the connection active when the
  70. * client sends some data or hearbeats (i.e. whitespaces) to the server.
  71. * The reason for this is that sending data will fail if the connection is closed. And if
  72. * the thread is blocked while sending data (because the socket is closed) then the clean up
  73. * thread will close the socket anyway.
  74. */
  75. private long idleTimeout = -1;
  76. final private Map<ConnectionCloseListener, Object> listeners =
  77. new HashMap<ConnectionCloseListener, Object>();
  78. private Socket socket;
  79. private SocketReader socketReader;
  80. private Writer writer;
  81. private AtomicBoolean writing = new AtomicBoolean(false);
  82. /**
  83. * Deliverer to use when the connection is closed or was closed when delivering
  84. * a packet.
  85. */
  86. private PacketDeliverer backupDeliverer;
  87. private LocalSession session;
  88. private boolean secure;
  89. private boolean compressed;
  90. private org.jivesoftware.util.XMLWriter xmlSerializer;
  91. private boolean flashClient = false;
  92. private int majorVersion = 1;
  93. private int minorVersion = 0;
  94. private String language = null;
  95. private TLSStreamHandler tlsStreamHandler;
  96. private long writeStarted = -1;
  97. /**
  98. * TLS policy currently in use for this connection.
  99. */
  100. private TLSPolicy tlsPolicy = TLSPolicy.optional;
  101. private boolean usingSelfSignedCertificate;
  102. /**
  103. * Compression policy currently in use for this connection.
  104. */
  105. private CompressionPolicy compressionPolicy = CompressionPolicy.disabled;
  106. public static Collection<SocketConnection> getInstances() {
  107. return instances.keySet();
  108. }
  109. /**
  110. * Create a new session using the supplied socket.
  111. *
  112. * @param backupDeliverer the packet deliverer this connection will use when socket is closed.
  113. * @param socket the socket to represent.
  114. * @param isSecure true if this is a secure connection.
  115. * @throws java.io.IOException if there was a socket error while sending the packet.
  116. */
  117. public SocketConnection(PacketDeliverer backupDeliverer, Socket socket, boolean isSecure)
  118. throws IOException {
  119. if (socket == null) {
  120. throw new NullPointerException("Socket channel must be non-null");
  121. }
  122. this.secure = isSecure;
  123. this.socket = socket;
  124. // DANIELE: Modify socket to use channel
  125. if (socket.getChannel() != null) {
  126. writer = Channels.newWriter(
  127. ServerTrafficCounter.wrapWritableChannel(socket.getChannel()), CHARSET);
  128. }
  129. else {
  130. writer = new BufferedWriter(new OutputStreamWriter(
  131. ServerTrafficCounter.wrapOutputStream(socket.getOutputStream()), CHARSET));
  132. }
  133. this.backupDeliverer = backupDeliverer;
  134. xmlSerializer = new XMLSocketWriter(writer, this);
  135. instances.put(this, "");
  136. }
  137. /**
  138. * Returns the stream handler responsible for securing the plain connection and providing
  139. * the corresponding input and output streams.
  140. *
  141. * @return the stream handler responsible for securing the plain connection and providing
  142. * the corresponding input and output streams.
  143. */
  144. public TLSStreamHandler getTLSStreamHandler() {
  145. return tlsStreamHandler;
  146. }
  147. public void startTLS(boolean clientMode, String remoteServer, ClientAuth authentication) throws IOException {
  148. if (!secure) {
  149. secure = true;
  150. // Prepare for TLS
  151. tlsStreamHandler = new TLSStreamHandler(this, socket, clientMode, remoteServer,
  152. session instanceof IncomingServerSession);
  153. if (!clientMode) {
  154. // Indicate the client that the server is ready to negotiate TLS
  155. deliverRawText("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
  156. }
  157. // Start handshake
  158. tlsStreamHandler.start();
  159. // Use new wrapped writers
  160. writer = new BufferedWriter(new OutputStreamWriter(tlsStreamHandler.getOutputStream(), CHARSET));
  161. xmlSerializer = new XMLSocketWriter(writer, this);
  162. }
  163. }
  164. public void addCompression() {
  165. // WARNING: We do not support adding compression for incoming traffic but not for outgoing traffic.
  166. }
  167. public void startCompression() {
  168. compressed = true;
  169. try {
  170. if (tlsStreamHandler == null) {
  171. ZOutputStream out = new ZOutputStream(
  172. ServerTrafficCounter.wrapOutputStream(socket.getOutputStream()),
  173. JZlib.Z_BEST_COMPRESSION);
  174. out.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
  175. writer = new BufferedWriter(new OutputStreamWriter(out, CHARSET));
  176. xmlSerializer = new XMLSocketWriter(writer, this);
  177. }
  178. else {
  179. ZOutputStream out = new ZOutputStream(tlsStreamHandler.getOutputStream(), JZlib.Z_BEST_COMPRESSION);
  180. out.setFlushMode(JZlib.Z_PARTIAL_FLUSH);
  181. writer = new BufferedWriter(new OutputStreamWriter(out, CHARSET));
  182. xmlSerializer = new XMLSocketWriter(writer, this);
  183. }
  184. } catch (IOException e) {
  185. // TODO Would be nice to still be able to throw the exception and not catch it here
  186. Log.error("Error while starting compression", e);
  187. compressed = false;
  188. }
  189. }
  190. public boolean validate() {
  191. if (isClosed()) {
  192. return false;
  193. }
  194. boolean allowedToWrite = false;
  195. try {
  196. requestWriting();
  197. allowedToWrite = true;
  198. // Register that we started sending data on the connection
  199. writeStarted();
  200. writer.write(" ");
  201. writer.flush();
  202. }
  203. catch (Exception e) {
  204. Log.warn("Closing no longer valid connection" + "\n" + this.toString(), e);
  205. close();
  206. }
  207. finally {
  208. // Register that we finished sending data on the connection
  209. writeFinished();
  210. if (allowedToWrite) {
  211. releaseWriting();
  212. }
  213. }
  214. return !isClosed();
  215. }
  216. public void init(LocalSession owner) {
  217. session = owner;
  218. }
  219. public void registerCloseListener(ConnectionCloseListener listener, Object handbackMessage) {
  220. if (isClosed()) {
  221. listener.onConnectionClose(handbackMessage);
  222. }
  223. else {
  224. listeners.put(listener, handbackMessage);
  225. }
  226. }
  227. public void removeCloseListener(ConnectionCloseListener listener) {
  228. listeners.remove(listener);
  229. }
  230. public byte[] getAddress() throws UnknownHostException {
  231. return socket.getInetAddress().getAddress();
  232. }
  233. public String getHostAddress() throws UnknownHostException {
  234. return socket.getInetAddress().getHostAddress();
  235. }
  236. public String getHostName() throws UnknownHostException {
  237. return socket.getInetAddress().getHostName();
  238. }
  239. /**
  240. * Returns the port that the connection uses.
  241. *
  242. * @return the port that the connection uses.
  243. */
  244. public int getPort() {
  245. return socket.getPort();
  246. }
  247. /**
  248. * Returns the Writer used to send data to the connection. The writer should be
  249. * used with caution. In the majority of cases, the {@link #deliver(Packet)}
  250. * method should be used to send data instead of using the writer directly.
  251. * You must synchronize on the writer before writing data to it to ensure
  252. * data consistency:
  253. *
  254. * <pre>
  255. * Writer writer = connection.getWriter();
  256. * synchronized(writer) {
  257. * // write data....
  258. * }</pre>
  259. *
  260. * @return the Writer for this connection.
  261. */
  262. public Writer getWriter() {
  263. return writer;
  264. }
  265. public boolean isClosed() {
  266. if (session == null) {
  267. return socket.isClosed();
  268. }
  269. return session.getStatus() == Session.STATUS_CLOSED;
  270. }
  271. public boolean isSecure() {
  272. return secure;
  273. }
  274. public boolean isCompressed() {
  275. return compressed;
  276. }
  277. public TLSPolicy getTlsPolicy() {
  278. return tlsPolicy;
  279. }
  280. /**
  281. * Sets whether TLS is mandatory, optional or is disabled. When TLS is mandatory clients
  282. * are required to secure their connections or otherwise their connections will be closed.
  283. * On the other hand, when TLS is disabled clients are not allowed to secure their connections
  284. * using TLS. Their connections will be closed if they try to secure the connection. in this
  285. * last case.
  286. *
  287. * @param tlsPolicy whether TLS is mandatory, optional or is disabled.
  288. */
  289. public void setTlsPolicy(TLSPolicy tlsPolicy) {
  290. this.tlsPolicy = tlsPolicy;
  291. }
  292. public CompressionPolicy getCompressionPolicy() {
  293. return compressionPolicy;
  294. }
  295. /**
  296. * Sets whether compression is enabled or is disabled.
  297. *
  298. * @param compressionPolicy whether Compression is enabled or is disabled.
  299. */
  300. public void setCompressionPolicy(CompressionPolicy compressionPolicy) {
  301. this.compressionPolicy = compressionPolicy;
  302. }
  303. public long getIdleTimeout() {
  304. return idleTimeout;
  305. }
  306. /**
  307. * Sets the number of milliseconds a connection has to be idle to be closed. Sending
  308. * stanzas to the client is not considered as activity. We are only considering the
  309. * connection active when the client sends some data or hearbeats (i.e. whitespaces)
  310. * to the server.
  311. *
  312. * @param timeout the number of milliseconds a connection has to be idle to be closed.
  313. */
  314. public void setIdleTimeout(long timeout) {
  315. this.idleTimeout = timeout;
  316. }
  317. public int getMajorXMPPVersion() {
  318. return majorVersion;
  319. }
  320. public int getMinorXMPPVersion() {
  321. return minorVersion;
  322. }
  323. /**
  324. * Sets the XMPP version information. In most cases, the version should be "1.0".
  325. * However, older clients using the "Jabber" protocol do not set a version. In that
  326. * case, the version is "0.0".
  327. *
  328. * @param majorVersion the major version.
  329. * @param minorVersion the minor version.
  330. */
  331. public void setXMPPVersion(int majorVersion, int minorVersion) {
  332. this.majorVersion = majorVersion;
  333. this.minorVersion = minorVersion;
  334. }
  335. public String getLanguage() {
  336. return language;
  337. }
  338. /**
  339. * Sets the language code that should be used for this connection (e.g. "en").
  340. *
  341. * @param language the language code.
  342. */
  343. public void setLanaguage(String language) {
  344. this.language = language;
  345. }
  346. public boolean isFlashClient() {
  347. return flashClient;
  348. }
  349. /**
  350. * Sets whether the connected client is a flash client. Flash clients need to
  351. * receive a special character (i.e. \0) at the end of each xml packet. Flash
  352. * clients may send the character \0 in incoming packets and may start a
  353. * connection using another openning tag such as: "flash:client".
  354. *
  355. * @param flashClient true if the if the connection is a flash client.
  356. */
  357. public void setFlashClient(boolean flashClient) {
  358. this.flashClient = flashClient;
  359. }
  360. public Certificate[] getLocalCertificates() {
  361. if (tlsStreamHandler != null) {
  362. return tlsStreamHandler.getSSLSession().getLocalCertificates();
  363. }
  364. return new Certificate[0];
  365. }
  366. public Certificate[] getPeerCertificates() {
  367. if (tlsStreamHandler != null) {
  368. try {
  369. return tlsStreamHandler.getSSLSession().getPeerCertificates();
  370. } catch (SSLPeerUnverifiedException e ) {
  371. Log.warn("Error retrieving client certificates of: " + tlsStreamHandler.getSSLSession(), e);
  372. //pretend tlsStreamHandler is null
  373. }
  374. }
  375. return new Certificate[0];
  376. }
  377. public void setUsingSelfSignedCertificate(boolean isSelfSigned) {
  378. this.usingSelfSignedCertificate = isSelfSigned;
  379. }
  380. public boolean isUsingSelfSignedCertificate() {
  381. return usingSelfSignedCertificate;
  382. }
  383. public PacketDeliverer getPacketDeliverer() {
  384. return backupDeliverer;
  385. }
  386. public void close() {
  387. boolean wasClosed = false;
  388. synchronized (this) {
  389. if (!isClosed()) {
  390. try {
  391. if (session != null) {
  392. session.setStatus(Session.STATUS_CLOSED);
  393. }
  394. boolean allowedToWrite = false;
  395. try {
  396. requestWriting();
  397. allowedToWrite = true;
  398. // Register that we started sending data on the connection
  399. writeStarted();
  400. writer.write("</stream:stream>");
  401. if (flashClient) {
  402. writer.write('\0');
  403. }
  404. writer.flush();
  405. }
  406. catch (IOException e) {
  407. // Do nothing
  408. }
  409. finally {
  410. // Register that we finished sending data on the connection
  411. writeFinished();
  412. if (allowedToWrite) {
  413. releaseWriting();
  414. }
  415. }
  416. }
  417. catch (Exception e) {
  418. Log.error(LocaleUtils.getLocalizedString("admin.error.close")
  419. + "\n" + this.toString(), e);
  420. }
  421. closeConnection();
  422. wasClosed = true;
  423. }
  424. }
  425. if (wasClosed) {
  426. notifyCloseListeners();
  427. }
  428. }
  429. public void systemShutdown() {
  430. deliverRawText("<stream:error><system-shutdown " +
  431. "xmlns='urn:ietf:params:xml:ns:xmpp-streams'/></stream:error>");
  432. close();
  433. }
  434. void writeStarted() {
  435. writeStarted = System.currentTimeMillis();
  436. }
  437. void writeFinished() {
  438. writeStarted = -1;
  439. }
  440. /**
  441. * Returns true if the socket was closed due to a bad health. The socket is considered to
  442. * be in a bad state if a thread has been writing for a while and the write operation has
  443. * not finished in a long time or when the client has not sent a heartbeat for a long time.
  444. * In any of both cases the socket will be closed.
  445. *
  446. * @return true if the socket was closed due to a bad health.s
  447. */
  448. boolean checkHealth() {
  449. // Check that the sending operation is still active
  450. long writeTimestamp = writeStarted;
  451. if (writeTimestamp > -1 && System.currentTimeMillis() - writeTimestamp >
  452. JiveGlobals.getIntProperty("xmpp.session.sending-limit", 60000)) {
  453. // Close the socket
  454. if (Log.isDebugEnabled()) {
  455. Log.debug("Closing connection: " + this + " that started sending data at: " +
  456. new Date(writeTimestamp));
  457. }
  458. forceClose();
  459. return true;
  460. }
  461. else {
  462. // Check if the connection has been idle. A connection is considered idle if the client
  463. // has not been receiving data for a period. Sending data to the client is not
  464. // considered as activity.
  465. if (idleTimeout > -1 && socketReader != null &&
  466. System.currentTimeMillis() - socketReader.getLastActive() > idleTimeout) {
  467. // Close the socket
  468. if (Log.isDebugEnabled()) {
  469. Log.debug("Closing connection that has been idle: " + this);
  470. }
  471. forceClose();
  472. return true;
  473. }
  474. }
  475. return false;
  476. }
  477. private void release() {
  478. writeStarted = -1;
  479. instances.remove(this);
  480. }
  481. /**
  482. * Forces the connection to be closed immediately no matter if closing the socket takes
  483. * a long time. This method should only be called from {@link SocketSendingTracker} when
  484. * sending data over the socket has taken a long time and we need to close the socket, discard
  485. * the connection and its session.
  486. */
  487. private void forceClose() {
  488. if (session != null) {
  489. // Set that the session is closed. This will prevent threads from trying to
  490. // deliver packets to this session thus preventing future locks.
  491. session.setStatus(Session.STATUS_CLOSED);
  492. }
  493. closeConnection();
  494. // Notify the close listeners so that the SessionManager can send unavailable
  495. // presences if required.
  496. notifyCloseListeners();
  497. }
  498. private void closeConnection() {
  499. release();
  500. try {
  501. if (tlsStreamHandler == null) {
  502. socket.close();
  503. }
  504. else {
  505. // Close the channels since we are using TLS (i.e. NIO). If the channels implement
  506. // the InterruptibleChannel interface then any other thread that was blocked in
  507. // an I/O operation will be interrupted and an exception thrown
  508. tlsStreamHandler.close();
  509. }
  510. }
  511. catch (Exception e) {
  512. Log.error(LocaleUtils.getLocalizedString("admin.error.close")
  513. + "\n" + this.toString(), e);
  514. }
  515. }
  516. public void deliver(Packet packet) throws UnauthorizedException, PacketException {
  517. if (isClosed()) {
  518. backupDeliverer.deliver(packet);
  519. }
  520. else {
  521. boolean errorDelivering = false;
  522. boolean allowedToWrite = false;
  523. try {
  524. requestWriting();
  525. allowedToWrite = true;
  526. xmlSerializer.write(packet.getElement());
  527. if (flashClient) {
  528. writer.write('\0');
  529. }
  530. xmlSerializer.flush();
  531. }
  532. catch (Exception e) {
  533. Log.debug("Error delivering packet" + "\n" + this.toString(), e);
  534. errorDelivering = true;
  535. }
  536. finally {
  537. if (allowedToWrite) {
  538. releaseWriting();
  539. }
  540. }
  541. if (errorDelivering) {
  542. close();
  543. // Retry sending the packet again. Most probably if the packet is a
  544. // Message it will be stored offline
  545. backupDeliverer.deliver(packet);
  546. }
  547. else {
  548. session.incrementServerPacketCount();
  549. }
  550. }
  551. }
  552. public void deliverRawText(String text) {
  553. if (!isClosed()) {
  554. boolean errorDelivering = false;
  555. boolean allowedToWrite = false;
  556. try {
  557. requestWriting();
  558. allowedToWrite = true;
  559. // Register that we started sending data on the connection
  560. writeStarted();
  561. writer.write(text);
  562. if (flashClient) {
  563. writer.write('\0');
  564. }
  565. writer.flush();
  566. }
  567. catch (Exception e) {
  568. Log.debug("Error delivering raw text" + "\n" + this.toString(), e);
  569. errorDelivering = true;
  570. }
  571. finally {
  572. // Register that we finished sending data on the connection
  573. writeFinished();
  574. if (allowedToWrite) {
  575. releaseWriting();
  576. }
  577. }
  578. if (errorDelivering) {
  579. close();
  580. }
  581. }
  582. }
  583. /**
  584. * Notifies all close listeners that the connection has been closed.
  585. * Used by subclasses to properly finish closing the connection.
  586. */
  587. private void notifyCloseListeners() {
  588. synchronized (listeners) {
  589. for (ConnectionCloseListener listener : listeners.keySet()) {
  590. try {
  591. listener.onConnectionClose(listeners.get(listener));
  592. }
  593. catch (Exception e) {
  594. Log.error("Error notifying listener: " + listener, e);
  595. }
  596. }
  597. }
  598. }
  599. private void requestWriting() throws Exception {
  600. for (;;) {
  601. if (writing.compareAndSet(false, true)) {
  602. // We are now in writing mode and only we can write to the socket
  603. return;
  604. }
  605. else {
  606. // Check health of the socket
  607. if (checkHealth()) {
  608. // Connection was closed then stop
  609. throw new Exception("Probable dead connection was closed");
  610. }
  611. else {
  612. Thread.sleep(1);
  613. }
  614. }
  615. }
  616. }
  617. private void releaseWriting() {
  618. writing.compareAndSet(true, false);
  619. }
  620. @Override
  621. public String toString() {
  622. return super.toString() + " socket: " + socket + " session: " + session;
  623. }
  624. public void setSocketReader(SocketReader socketReader) {
  625. this.socketReader = socketReader;
  626. }
  627. }