/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev$
* Last modified by $Author$
* $Date$
*/
package tigase.io;
//~--- non-JDK imports --------------------------------------------------------
import tigase.stats.StatisticsList;
//~--- JDK imports ------------------------------------------------------------
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
//~--- classes ----------------------------------------------------------------
/**
* Describe class TLSIO here.
*
*
* Created: Sat May 14 07:43:30 2005
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class TLSIO implements IOInterface {
/** Field description */
public static final String TLS_CAPS = "tls-caps";
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log = Logger.getLogger(TLSIO.class.getName());
// ~--- fields ---------------------------------------------------------------
private IOInterface io = null;
/**
* <code>tlsInput</code> buffer keeps data decoded from tlsWrapper.
*/
private ByteBuffer tlsInput = null;
/**
* <code>tlsWrapper</code> is a TLS wrapper for connections requiring TLS
* protocol.
*/
private TLSWrapper tlsWrapper = null;
// ~--- constructors ---------------------------------------------------------
// /**
// * Creates a new <code>TLSIO</code> instance.
// *
// */
// public TLSIO(final SocketChannel sock) {
// io = new SocketIO(sock);
// tlsWrapper = new TLSWrapper("TLS");
// tlsInput = ByteBuffer.allocate(tlsWrapper.getAppBuffSize());
// }
/**
* Constructs ...
*
*
* @param ioi
* @param wrapper
*
* @throws IOException
*/
public TLSIO(final IOInterface ioi, final TLSWrapper wrapper) throws IOException {
io = ioi;
tlsWrapper = wrapper;
tlsWrapper.setDebugId(toString());
tlsInput = ByteBuffer.allocate(tlsWrapper.getAppBuffSize());
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "TLS Socket created: {0}", io.toString());
}
if (tlsWrapper.isClientMode()) {
if (log.isLoggable(Level.FINER)) {
log.finer("TLS - client mode, starting handshaking now...");
}
write(ByteBuffer.allocate(0));
} // end of if (tlsWrapper.isClientMode())
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public int bytesRead() {
return io.bytesRead();
}
/**
* Method description
*
*
* @param caps
*
* @return
*/
@Override
public boolean checkCapabilities(String caps) {
return caps.contains(TLS_CAPS) || io.checkCapabilities(caps);
}
// ~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*
* @throws IOException
*/
@Override
public int getInputPacketSize() throws IOException {
return tlsWrapper.getPacketBuffSize();
}
/**
* Method description
*
*
* @return
*/
@Override
public SocketChannel getSocketChannel() {
return io.getSocketChannel();
}
/**
* Method description
*
*
* @param list
* @param reset
*/
@Override
public void getStatistics(StatisticsList list, boolean reset) {
if (io != null) {
io.getStatistics(list, reset);
}
}
/**
* Method description
*
*
* @return
*/
@Override
public boolean isConnected() {
return io.isConnected();
}
/**
* Method description
*
*
* @param addr
*
* @return
*/
@Override
public boolean isRemoteAddress(String addr) {
return io.isRemoteAddress(addr);
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param buff
*
* @return
*
* @throws IOException
*/
@Override
public ByteBuffer read(ByteBuffer buff) throws IOException {
// if (log.isLoggable(Level.FINER)) {
// log.finer("input.capacity()=" + buff.capacity());
// log.finer("input.remaining()=" + buff.remaining());
// log.finer("input.limit()=" + buff.limit());
// log.finer("input.position()=" + buff.position());
// }
ByteBuffer tmpBuffer = io.read(buff);
if (io.bytesRead() > 0) {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "Read bytes: {0}, {1}", new Object[] { io.bytesRead(),
toString() });
}
return decodeData(tmpBuffer);
} else {
return null;
} // end of else
}
/**
* Method description
*
*
* @throws IOException
*/
@Override
public void stop() throws IOException {
if (log.isLoggable(Level.FINEST)) {
log.finest("Stop called..." + toString());
// Thread.dumpStack();
}
io.stop();
tlsWrapper.close();
}
/**
* Method description
*
*
* @return
*/
@Override
public String toString() {
return "TLS: " + io.toString();
}
/**
* Method description
*
*
* @return
*/
@Override
public boolean waitingToSend() {
return io.waitingToSend();
}
/**
* Method description
*
*
* @return
*/
@Override
public int waitingToSendSize() {
return io.waitingToSendSize();
}
/**
* Method description
*
*
* @param buff
*
* @return
*
* @throws IOException
*/
@Override
public int write(ByteBuffer buff) throws IOException {
TLSStatus stat = tlsWrapper.getStatus();
// The loop below falls into infinite loop for some reason.
// Let's try to detect it here and recover.
// Looks like for some reason tlsWrapper.getStatus() sometimes starts to
// return
// NEED_READ status all the time and the loop never ends.
int loop_cnt = 0;
int max_loop_runs = 1000;
while (((stat == TLSStatus.NEED_WRITE) || (stat == TLSStatus.NEED_READ))
&& (++loop_cnt < max_loop_runs)) {
switch (stat) {
case NEED_WRITE:
writeBuff(ByteBuffer.allocate(0));
break;
case NEED_READ:
// I wonder if some real data can be read from the socket here (and we
// would
// loose the data) or this is just TLS stuff here.....
ByteBuffer rbuff = read(ByteBuffer.allocate(tlsWrapper.getNetBuffSize()));
break;
// case CLOSED:
// if (tlsWrapper.getStatus() == TLSStatus.CLOSED) {
// if (log.isLoggable(Level.FINER)) {
// log.finer("TLS Socket closed...");
// }
// throw new EOFException("Socket has been closed.");
// } // end of if (tlsWrapper.getStatus() == TLSStatus.CLOSED)
// break;
default:
}
stat = tlsWrapper.getStatus();
}
if (loop_cnt > (max_loop_runs / 2)) {
log.log(Level.WARNING,
"Infinite loop detected in write(buff) TLS code, tlsWrapper.getStatus(): {0}",
tlsWrapper.getStatus());
// Let's close the connection now
throw new EOFException("Socket has been closed due to TLS problems.");
}
if (tlsWrapper.getStatus() == TLSStatus.CLOSED) {
if (log.isLoggable(Level.FINER)) {
log.finer("TLS Socket closed...");
}
throw new EOFException("Socket has been closed.");
} // end of if (tlsWrapper.getStatus() == TLSStatus.CLOSED)
int result = -1;
if (buff == null) {
result = io.write(null);
} else {
if (log.isLoggable(Level.FINER)) {
log.log(Level.FINER, "TLS - Writing data, remaining: {0}, {1}", new Object[] {
buff.remaining(), toString() });
}
result = writeBuff(buff);
}
// if (isRemoteAddress("81.142.228.219")) {
// log.warning("TLS - Writing data, remaining: " + buff.remaining());
// }
// if (isRemoteAddress("81.142.228.219")) {
// log.warning("TLS - written: " + result);
// }
return result;
}
private ByteBuffer decodeData(ByteBuffer input) throws IOException {
TLSStatus stat = null;
boolean continueLoop = true;
// input.flip();
// do_loop:
do {
// if (log.isLoggable(Level.FINER)) {
// log.finer("Decoding data: " + input.remaining());
// log.finer("input.capacity()=" + input.capacity());
// log.finer("input.remaining()=" + input.remaining());
// log.finer("input.limit()=" + input.limit());
// log.finer("input.position()=" + input.position());
// log.finer("tlsInput.capacity()=" + tlsInput.capacity());
// log.finer("tlsInput.remaining()=" + tlsInput.remaining());
// log.finer("tlsInput.limit()=" + tlsInput.limit());
// log.finer("tlsInput.position()=" + tlsInput.position());
// }
tlsInput = tlsWrapper.unwrap(input, tlsInput);
// if (log.isLoggable(Level.FINEST)) {
// int netSize = tlsWrapper.getPacketBuffSize();
//
// log.finer("tlsWrapper.getStatus() = " + tlsWrapper.getStatus().name());
// log.finer("PacketBuffSize=" + netSize);
// log.finer("input.capacity()=" + input.capacity());
// log.finer("input.remaining()=" + input.remaining());
// log.finer("input.limit()=" + input.limit());
// log.finer("input.position()=" + input.position());
// log.finer("tlsInput.capacity()=" + tlsInput.capacity());
// log.finer("tlsInput.remaining()=" + tlsInput.remaining());
// log.finer("tlsInput.limit()=" + tlsInput.limit());
// log.finer("tlsInput.position()=" + tlsInput.position());
// }
// if (input.hasRemaining()) {
// input.compact();
// }// end of if (input.hasRemaining())
switch (tlsWrapper.getStatus()) {
case NEED_WRITE:
writeBuff(ByteBuffer.allocate(0));
break;
case UNDERFLOW:
// if (log.isLoggable(Level.FINER) && !log.isLoggable(Level.FINEST)) {
// int netSize = tlsWrapper.getPacketBuffSize();
// log.finer("tlsWrapper.getStatus() = UNDERFLOW");
// log.finer("PacketBuffSize=" + netSize);
// log.finer("input.capacity()=" + input.capacity());
// log.finer("input.remaining()=" + input.remaining());
// log.finer("input.limit()=" + input.limit());
// log.finer("input.position()=" + input.position());
// log.finer("tlsInput.capacity()=" + tlsInput.capacity());
// log.finer("tlsInput.remaining()=" + tlsInput.remaining());
// log.finer("tlsInput.limit()=" + tlsInput.limit());
// log.finer("tlsInput.position()=" + tlsInput.position());
// }
// Obtain more inbound network data for src,
// then retry the operation.
// If there is some data ready to read, let's try to read it before we
// increase
// the buffer size
// throw new BufferUnderflowException();
if (tlsInput.capacity() == tlsInput.remaining()) {
throw new BufferUnderflowException();
} else {
input.compact();
continueLoop = false;
}
break;
case CLOSED:
// if (tlsWrapper.getStatus() == TLSStatus.CLOSED) {
if (log.isLoggable(Level.FINER)) {
log.finer("TLS Socket closed..." + toString());
}
throw new EOFException("Socket has been closed.");
// } // end of if (tlsWrapper.getStatus() == TLSStatus.CLOSED)
// break do_loop;
// break;
default:
break;
} // end of switch (tlsWrapper.getStatus())
stat = tlsWrapper.getStatus();
} while (continueLoop && ((stat == TLSStatus.NEED_READ) || (stat == TLSStatus.OK))
&& input.hasRemaining());
if (continueLoop) {
if (input.hasRemaining()) {
input.rewind();
} else {
input.clear();
}
}
tlsInput.flip();
return tlsInput;
}
private int writeBuff(ByteBuffer buff) throws IOException {
int result = 0;
int wr = 0;
// The loop below falls into infinite loop for some reason.
// Let's try to detect it here and recover.
// -- After some tests....
// Looks like the cause has been detected. Sometimes the loop
// below is executed a few times for some reason. It happens that
// the tlsWarpper.getStatus() returns NEED_READ and it doesn't
// accept any more data from the input buffer.
// The proper handling would need reading from the socket to
// reset TLS to the correct state, but this involves another problems.
// What to do with possible user data received in such a call?
// It happens extremely rarely and is hard to diagnose. Let's leave it
// as it is now which just causes such connections to be closed.
int loop_cnt = 0;
int max_loop_runs = 1000;
do {
if (tlsWrapper.getStatus() == TLSStatus.NEED_READ) {
// I wonder if some real data can be read from the socket here (and we
// would
// loose the data) or this is just TLS stuff here.....
ByteBuffer rbuff = read(ByteBuffer.allocate(tlsWrapper.getNetBuffSize()));
}
ByteBuffer tlsOutput = ByteBuffer.allocate(tlsWrapper.getNetBuffSize());
// Not sure if this is really needed, I guess not...
tlsOutput.clear();
tlsWrapper.wrap(buff, tlsOutput);
if (tlsWrapper.getStatus() == TLSStatus.CLOSED) {
throw new EOFException("Socket has been closed.");
} // end of if (tlsWrapper.getStatus() == TLSStatus.CLOSED)
tlsOutput.flip();
wr = io.write(tlsOutput);
result += wr;
} while (buff.hasRemaining() && (++loop_cnt < max_loop_runs));
if (loop_cnt > (max_loop_runs / 2)) {
log.warning("Infinite loop detected in writeBuff(buff) TLS code, "
+ "tlsWrapper.getStatus(): " + tlsWrapper.getStatus());
// Let's close the connection now
throw new EOFException("Socket has been closed due to TLS problems.");
}
if (tlsWrapper.getStatus() == TLSStatus.NEED_WRITE) {
writeBuff(ByteBuffer.allocate(0));
} // end of if ()
return result;
}
/*
* (non-Javadoc)
*
* @see tigase.io.IOInterface#setLogId(java.lang.String)
*/
@Override
public void setLogId(String logId) {
io.setLogId(logId);
}
} // TLSIO
// ~ Formatted in Sun Code Convention
// ~ Formatted by Jindent --- http://www.jindent.com