package gnu.crypto.sasl; // ---------------------------------------------------------------------------- // $Id: SaslInputStream.java,v 1.3 2005/10/06 04:24:18 rsdio Exp $ // // Copyright (C) 2003 Free Software Foundation, Inc. // // This file is part of GNU Crypto. // // GNU Crypto is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2, or (at your option) // any later version. // // GNU Crypto 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 // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; see the file COPYING. If not, write to the // // Free Software Foundation Inc., // 51 Franklin Street, Fifth Floor, // Boston, MA 02110-1301 // USA // // Linking this library statically or dynamically with other modules is // making a combined work based on this library. Thus, the terms and // conditions of the GNU General Public License cover the whole // combination. // // As a special exception, the copyright holders of this library give // you permission to link this library with independent modules to // produce an executable, regardless of the license terms of these // independent modules, and to copy and distribute the resulting // executable under terms of your choice, provided that you also meet, // for each linked independent module, the terms and conditions of the // license of that module. An independent module is a module which is // not derived from or based on this library. If you modify this // library, you may extend this exception to your version of the // library, but you are not obligated to do so. If you do not wish to // do so, delete this exception statement from your version. // ---------------------------------------------------------------------------- import gnu.crypto.util.Util; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.IOException; import java.io.PrintWriter; import javax.security.sasl.Sasl; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslServer; /** * An input stream that uses either a {@link SaslClient} or a {@link SaslServer} * to process the data through these entities' security layer filter(s). * * @version $Revision: 1.3 $ */ public class SaslInputStream extends InputStream { // Debugging methods and variables // ------------------------------------------------------------------------- private static final String NAME = "SaslOutputStream"; private static final String ERROR = "ERROR"; private static final String WARN = " WARN"; // private static final String INFO = " INFO"; private static final String TRACE = "DEBUG"; private static final boolean DEBUG = true; private static final int debuglevel = 3; private static final PrintWriter err = new PrintWriter(System.out, true); private static void debug(String level, Object obj) { err.println("["+level+"] "+NAME+": "+String.valueOf(obj)); } // Constants and variables // ------------------------------------------------------------------------- private SaslClient client; private SaslServer server; private int maxRawSendSize; private InputStream source; private byte[] internalBuf; // Constructor(s) // ------------------------------------------------------------------------- public SaslInputStream(SaslClient client, InputStream source) throws IOException { super(); this.client = client; maxRawSendSize = Integer.parseInt( (String) client.getNegotiatedProperty(Sasl.RAW_SEND_SIZE)); server = null; this.source = source; } public SaslInputStream(SaslServer server, InputStream source) throws IOException { super(); this.server = server; maxRawSendSize = Integer.parseInt( (String) server.getNegotiatedProperty(Sasl.RAW_SEND_SIZE)); client = null; this.source = source; } // Class methods // ------------------------------------------------------------------------- // Instance methods // ------------------------------------------------------------------------- // Overloaded java.io.InputStream methods ---------------------------------- public int available() throws IOException { return (internalBuf == null) ? 0 : internalBuf.length; } public void close() throws IOException { source.close(); } /** * <p>Reads the next byte of data from the input stream. The value byte is * returned as an <code>int</code> in the range <code>0</code> to * <code>255</code>. If no byte is available because the end of the stream * has been reached, the value <code>-1</code> is returned. This method * blocks until input data is available, the end of the stream is detected, * or an exception is thrown.</p> * * <p>From a SASL mechanism provider's perspective, if a security layer has * been negotiated, the underlying <i>source</i> is expected to contain SASL * buffers, as defined in RFC 2222. Four octets in network byte order in the * front of each buffer identify the length of the buffer. The provider is * responsible for performing any integrity checking or other processing on * the buffer before returning the data as a stream of octets. For example, * the protocol driver's request for a single octet from the stream might; * i.e. an invocation of this method, may result in an entire SASL buffer * being read and processed before that single octet can be returned.</p> * * @return the next byte of data, or <code>-1</code> if the end of the stream * is reached. * @throws IOException if an I/O error occurs. */ public int read() throws IOException { int result = -1; if (internalBuf != null && internalBuf.length > 0) { result = internalBuf[0] & 0xFF; if (internalBuf.length == 1) internalBuf = new byte[0]; else { byte[] tmp = new byte[internalBuf.length - 1]; // System.arraycopy(internalBuf, 0, tmp, 0, tmp.length); System.arraycopy(internalBuf, 1, tmp, 0, tmp.length); internalBuf = tmp; } } else { byte[] buf = new byte[1]; int check = read(buf); result = (check > 0) ? (buf[0] & 0xFF) : -1; } return result; } /** * <p>Reads up to <code>len</code> bytes of data from the underlying * <i>source</i> input stream into an array of bytes. An attempt is made to * read as many as <code>len</code> bytes, but a smaller number may be read, * possibly zero. The number of bytes actually read is returned as an * integer.</p> * * <p>This method blocks until input data is available, end of file is * detected, or an exception is thrown.</p> * * <p>If <code>b</code> is <code>null</code>, a {@link NullPointerException} is * thrown.</p> * * <p>If <code>off</code> is negative, or <code>len</code> is negative, or * <code>off+len</code> is greater than the length of the array <code>b</code>, * then an {@link IndexOutOfBoundsException} is thrown.</p> * * <p>If <code>len</code> is zero, then no bytes are read and <code>0</code> * is returned; otherwise, there is an attempt to read at least one byte. If * no byte is available because the stream is at end of file, the value * <code>-1</code> is returned; otherwise, at least one byte is read and * stored into <code>b</code>.</p> * * <p>The first byte read is stored into element <code>b[off]</code>, the * next one into <code>b[off+1]</code>, and so on. The number of bytes read * is, at most, equal to <code>len</code>. Let <code>k</code> be the number * of bytes actually read; these bytes will be stored in elements * <code>b[off]</code> through <code>b[off+k-1]</code>, leaving elements * <code>b[off+k]</code> through <code>b[off+len-1]</code> unaffected.</p> * * <p>In every case, elements <code>b[0]</code> through <code>b[off]</code> * and elements <code>b[off+len]</code> through <code>b[b.length-1]</code> * are unaffected.</p> * * <p>If the first byte cannot be read for any reason other than end of file, * then an {@link IOException} is thrown. In particular, an {@link IOException} * is thrown if the input stream has been closed.</p> * * <p>From the SASL mechanism provider's perspective, if a security layer has * been negotiated, the underlying <i>source</i> is expected to contain SASL * buffers, as defined in RFC 2222. Four octets in network byte order in the * front of each buffer identify the length of the buffer. The provider is * responsible for performing any integrity checking or other processing on * the buffer before returning the data as a stream of octets. The protocol * driver's request for a single octet from the stream might result in an * entire SASL buffer being read and processed before that single octet can * be returned.</p> * * @param b the buffer into which the data is read. * @param off the start offset in array <code>b</code> at which the data is * wricodeen. * @param len the maximum number of bytes to read. * @return the total number of bytes read into the buffer, or <code>-1</code> * if there is no more data because the end of the stream has been reached. * @throws IOException if an I/O error occurs. */ public int read(byte[] b, int off, int len) throws IOException { if (DEBUG && debuglevel > 8) debug(TRACE, "==> read(b, "+String.valueOf(off)+", "+String.valueOf(len)+")"); if (b == null) { throw new NullPointerException("b"); } if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) || ((off + len) < 0)) { throw new IndexOutOfBoundsException("off="+String.valueOf(off) +", len="+String.valueOf(len)+", b.length="+String.valueOf(b.length)); } if (len == 0) { if (DEBUG && debuglevel > 8) debug(TRACE, "<== read() --> 0"); return 0; } if (DEBUG && debuglevel > 6) debug(TRACE, "Available: "+String.valueOf(available())); int result = 0; if (internalBuf == null || internalBuf.length < 1) try { internalBuf = readSaslBuffer(); if (internalBuf == null) { if (DEBUG && debuglevel > 4) debug(WARN, "Underlying stream empty. Returning -1"); if (DEBUG && debuglevel > 8) debug(TRACE, "<== read() --> -1"); return -1; } } catch (InterruptedIOException x) { if (DEBUG && debuglevel > 6) debug(TRACE, x); if (DEBUG && debuglevel > 4) debug(WARN, "Reading thread was interrupted. Returning -1"); if (DEBUG && debuglevel > 8) debug(TRACE, "<== read() --> -1"); return -1; } if (len <= internalBuf.length) { result = len; System.arraycopy(internalBuf, 0, b, off, len); if (len == internalBuf.length) internalBuf = null; else { byte[] tmp = new byte[internalBuf.length - len]; System.arraycopy(internalBuf, len, tmp, 0, tmp.length); internalBuf = tmp; } } else { // first copy the available bytes to b result = internalBuf.length; System.arraycopy(internalBuf, 0, b, off, result); internalBuf = null; off += result; len -= result; int remaining; // count of bytes remaining in buffer after an iteration int delta; // count of bytes moved to b after an iteration int datalen; byte[] data; while (len > 0) // we need to read SASL buffers, as long as there are at least // 4 bytes available at the source if (source.available() > 3) { // process a buffer data = readSaslBuffer(); if (data == null) { if (DEBUG && debuglevel > 4) debug(WARN, "Underlying stream exhausted. Breaking..."); break; } datalen = data.length; // copy [part of] the result to b remaining = (datalen <= len) ? 0 : datalen - len; delta = datalen - remaining; System.arraycopy(data, 0, b, off, delta); if (remaining > 0) { internalBuf = new byte[remaining]; System.arraycopy(data, delta, internalBuf, 0, remaining); } // update off, result and len off += delta; result += delta; len -= delta; } else { // nothing much we can do except return what we have if (DEBUG && debuglevel > 4) debug(WARN, "Not enough bytes in source to read a buffer. Breaking..."); break; } } if (DEBUG && debuglevel > 6) debug(TRACE, "Remaining: "+(internalBuf == null ? 0 : internalBuf.length)); if (DEBUG && debuglevel > 8) debug(TRACE, "<== read() --> "+String.valueOf(result)); return result; } // other nstance methods --------------------------------------------------- /** * Reads a SASL buffer from the underlying source if at least 4 bytes are * available. * * @return the byte[] of decoded buffer contents, or null if the underlying * source was exhausted. * @throws IOException if an I/O exception occurs during the operation. */ private byte[] readSaslBuffer() throws IOException { if (DEBUG && debuglevel > 8) debug(TRACE, "==> readSaslBuffer()"); int realLength; // check if we read as many bytes as we're supposed to byte[] result = new byte[4]; try { realLength = source.read(result); if (realLength == -1) { if (DEBUG && debuglevel > 8) debug(TRACE, "<== readSaslBuffer() --> null"); return null; } } catch (IOException x) { if (DEBUG && debuglevel > 0) debug(ERROR, x); throw x; } if (realLength != 4) { throw new IOException("Was expecting 4 but found "+String.valueOf(realLength)); } int bufferLength = result[0] << 24 | (result[1] & 0xFF) << 16 | (result[2] & 0xFF) << 8 | (result[3] & 0xFF); if (DEBUG && debuglevel > 6) debug(TRACE, "SASL buffer size: "+bufferLength); if (bufferLength > maxRawSendSize || bufferLength < 0) { throw new SaslEncodingException("SASL buffer (security layer) too long"); } result = new byte[bufferLength]; try { realLength = source.read(result); } catch (IOException x) { if (DEBUG && debuglevel > 0) debug(ERROR, x); throw x; } if (realLength != bufferLength) throw new IOException("Was expecting "+String.valueOf(bufferLength) +" but found "+String.valueOf(realLength)); if (DEBUG && debuglevel > 6) debug(TRACE, "Incoming buffer (before security) (hex): "+Util.dumpString(result)); if (DEBUG && debuglevel > 6) debug(TRACE, "Incoming buffer (before security) (str): \""+new String(result)+"\""); if (client != null) { result = client.unwrap(result, 0, realLength); } else { result = server.unwrap(result, 0, realLength); } if (DEBUG && debuglevel > 6) debug(TRACE, "Incoming buffer (after security) (hex): "+Util.dumpString(result)); if (DEBUG && debuglevel > 6) debug(TRACE, "Incoming buffer (after security) (str): \""+new String(result)+"\""); if (DEBUG && debuglevel > 8) debug(TRACE, "<== readSaslBuffer()"); return result; } }