Source for gnu.javax.crypto.sasl.SaslInputStream

   1: /* SaslInputStream.java --
   2:    Copyright (C) 2003, 2006 Free Software Foundation, Inc.
   3: 
   4: This file is a part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2 of the License, or (at
   9: your option) any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; if not, write to the Free Software
  18: Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  19: USA
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version.  */
  37: 
  38: 
  39: package gnu.javax.crypto.sasl;
  40: 
  41: import gnu.java.security.Configuration;
  42: import gnu.java.security.util.Util;
  43: 
  44: import java.io.IOException;
  45: import java.io.InputStream;
  46: import java.io.InterruptedIOException;
  47: import java.util.logging.Logger;
  48: 
  49: import javax.security.sasl.Sasl;
  50: import javax.security.sasl.SaslClient;
  51: import javax.security.sasl.SaslServer;
  52: 
  53: /**
  54:  * An input stream that uses either a {@link SaslClient} or a {@link SaslServer}
  55:  * to process the data through these entities' security layer filter(s).
  56:  */
  57: public class SaslInputStream
  58:     extends InputStream
  59: {
  60:   private static final Logger log = Logger.getLogger(SaslInputStream.class.getName());
  61:   private SaslClient client;
  62:   private SaslServer server;
  63:   private int maxRawSendSize;
  64:   private InputStream source;
  65:   private byte[] internalBuf;
  66: 
  67:   public SaslInputStream(SaslClient client, InputStream source)
  68:       throws IOException
  69:   {
  70:     super();
  71: 
  72:     this.client = client;
  73:     String size = (String) client.getNegotiatedProperty(Sasl.RAW_SEND_SIZE);
  74:     maxRawSendSize = Integer.parseInt(size);
  75:     server = null;
  76:     this.source = source;
  77:   }
  78: 
  79:   public SaslInputStream(SaslServer server, InputStream source)
  80:       throws IOException
  81:   {
  82:     super();
  83: 
  84:     this.server = server;
  85:     String size = (String) server.getNegotiatedProperty(Sasl.RAW_SEND_SIZE);
  86:     maxRawSendSize = Integer.parseInt(size);
  87:     client = null;
  88:     this.source = source;
  89:   }
  90: 
  91:   public int available() throws IOException
  92:   {
  93:     return (internalBuf == null) ? 0 : internalBuf.length;
  94:   }
  95: 
  96:   public void close() throws IOException
  97:   {
  98:     source.close();
  99:   }
 100: 
 101:   /**
 102:    * Reads the next byte of data from the input stream. The value byte is
 103:    * returned as an <code>int</code> in the range <code>0</code> to
 104:    * <code>255</code>. If no byte is available because the end of the stream
 105:    * has been reached, the value <code>-1</code> is returned. This method
 106:    * blocks until input data is available, the end of the stream is detected, or
 107:    * an exception is thrown.
 108:    * <p>
 109:    * From a SASL mechanism provider's perspective, if a security layer has been
 110:    * negotiated, the underlying <i>source</i> is expected to contain SASL
 111:    * buffers, as defined in RFC 2222. Four octets in network byte order in the
 112:    * front of each buffer identify the length of the buffer. The provider is
 113:    * responsible for performing any integrity checking or other processing on
 114:    * the buffer before returning the data as a stream of octets. For example,
 115:    * the protocol driver's request for a single octet from the stream might;
 116:    * i.e. an invocation of this method, may result in an entire SASL buffer
 117:    * being read and processed before that single octet can be returned.
 118:    *
 119:    * @return the next byte of data, or <code>-1</code> if the end of the
 120:    *         stream is reached.
 121:    * @throws IOException if an I/O error occurs.
 122:    */
 123:   public int read() throws IOException
 124:   {
 125:     int result = -1;
 126:     if (internalBuf != null && internalBuf.length > 0)
 127:       {
 128:         result = internalBuf[0] & 0xFF;
 129:         if (internalBuf.length == 1)
 130:           internalBuf = new byte[0];
 131:         else
 132:           {
 133:             byte[] tmp = new byte[internalBuf.length - 1];
 134:             System.arraycopy(internalBuf, 1, tmp, 0, tmp.length);
 135:             internalBuf = tmp;
 136:           }
 137:       }
 138:     else
 139:       {
 140:         byte[] buf = new byte[1];
 141:         int check = read(buf);
 142:         result = (check > 0) ? (buf[0] & 0xFF) : -1;
 143:       }
 144:     return result;
 145:   }
 146: 
 147:   /**
 148:    * Reads up to <code>len</code> bytes of data from the underlying <i>source</i>
 149:    * input stream into an array of bytes. An attempt is made to read as many as
 150:    * <code>len</code> bytes, but a smaller number may be read, possibly zero.
 151:    * The number of bytes actually read is returned as an integer.
 152:    * <p>
 153:    * This method blocks until input data is available, end of file is detected,
 154:    * or an exception is thrown.
 155:    * <p>
 156:    * If <code>b</code> is <code>null</code>, a {@link NullPointerException}
 157:    * is thrown.
 158:    * <p>
 159:    * If <code>off</code> is negative, or <code>len</code> is negative, or
 160:    * <code>off+len</code> is greater than the length of the array
 161:    * <code>b</code>, then an {@link IndexOutOfBoundsException} is thrown.
 162:    * <p>
 163:    * If <code>len</code> is zero, then no bytes are read and <code>0</code>
 164:    * is returned; otherwise, there is an attempt to read at least one byte. If
 165:    * no byte is available because the stream is at end of file, the value
 166:    * <code>-1</code> is returned; otherwise, at least one byte is read and
 167:    * stored into <code>b</code>.
 168:    * <p>
 169:    * The first byte read is stored into element <code>b[off]</code>, the next
 170:    * one into <code>b[off+1]</code>, and so on. The number of bytes read is,
 171:    * at most, equal to <code>len</code>. Let <code>k</code> be the number
 172:    * of bytes actually read; these bytes will be stored in elements
 173:    * <code>b[off]</code> through <code>b[off+k-1]</code>, leaving elements
 174:    * <code>b[off+k]</code> through <code>b[off+len-1]</code> unaffected.
 175:    * <p>
 176:    * In every case, elements <code>b[0]</code> through <code>b[off]</code>
 177:    * and elements <code>b[off+len]</code> through <code>b[b.length-1]</code>
 178:    * are unaffected.
 179:    * <p>
 180:    * If the first byte cannot be read for any reason other than end of file,
 181:    * then an {@link IOException} is thrown. In particular, an
 182:    * {@link IOException} is thrown if the input stream has been closed.
 183:    * <p>
 184:    * From the SASL mechanism provider's perspective, if a security layer has
 185:    * been negotiated, the underlying <i>source</i> is expected to contain SASL
 186:    * buffers, as defined in RFC 2222. Four octets in network byte order in the
 187:    * front of each buffer identify the length of the buffer. The provider is
 188:    * responsible for performing any integrity checking or other processing on
 189:    * the buffer before returning the data as a stream of octets. The protocol
 190:    * driver's request for a single octet from the stream might result in an
 191:    * entire SASL buffer being read and processed before that single octet can be
 192:    * returned.
 193:    *
 194:    * @param b the buffer into which the data is read.
 195:    * @param off the start offset in array <code>b</code> at which the data is
 196:    *          wricodeen.
 197:    * @param len the maximum number of bytes to read.
 198:    * @return the total number of bytes read into the buffer, or <code>-1</code>
 199:    *         if there is no more data because the end of the stream has been
 200:    *         reached.
 201:    * @throws IOException if an I/O error occurs.
 202:    */
 203:   public int read(byte[] b, int off, int len) throws IOException
 204:   {
 205:     if (Configuration.DEBUG)
 206:       log.entering(this.getClass().getName(), "read", new Object[] {
 207:           b, Integer.valueOf(off), Integer.valueOf(len)
 208:       });
 209:     if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length)
 210:         || ((off + len) < 0))
 211:       throw new IndexOutOfBoundsException("off=" + off + ", len=" + len
 212:                                           + ", b.length=" + b.length);
 213:     if (len == 0)
 214:       {
 215:         if (Configuration.DEBUG)
 216:           log.exiting(this.getClass().getName(), "read", Integer.valueOf(0));
 217:         return 0;
 218:       }
 219:     if (Configuration.DEBUG)
 220:       log.finer("Available: " + available());
 221:     int result = 0;
 222:     if (internalBuf == null || internalBuf.length < 1)
 223:       try
 224:         {
 225:           internalBuf = readSaslBuffer();
 226:           if (internalBuf == null)
 227:             {
 228:               if (Configuration.DEBUG)
 229:                 {
 230:                   log.finer("Underlying stream empty. Returning -1");
 231:                   log.exiting(this.getClass().getName(), "read",
 232:                               Integer.valueOf(-1));
 233:                 }
 234:               return -1;
 235:             }
 236:         }
 237:       catch (InterruptedIOException x)
 238:         {
 239:           if (Configuration.DEBUG)
 240:             {
 241:               log.finer("Reading thread was interrupted. Returning -1");
 242:               log.throwing(this.getClass().getName(), "read", x);
 243:               log.exiting(this.getClass().getName(), "read",
 244:                           Integer.valueOf(-1));
 245:             }
 246:           return -1;
 247:         }
 248:     if (len <= internalBuf.length)
 249:       {
 250:         result = len;
 251:         System.arraycopy(internalBuf, 0, b, off, len);
 252:         if (len == internalBuf.length)
 253:           internalBuf = null;
 254:         else
 255:           {
 256:             byte[] tmp = new byte[internalBuf.length - len];
 257:             System.arraycopy(internalBuf, len, tmp, 0, tmp.length);
 258:             internalBuf = tmp;
 259:           }
 260:       }
 261:     else
 262:       {
 263:         // first copy the available bytes to b
 264:         result = internalBuf.length;
 265:         System.arraycopy(internalBuf, 0, b, off, result);
 266:         internalBuf = null;
 267:         off += result;
 268:         len -= result;
 269:         int remaining; // count of bytes remaining in buffer after an iteration
 270:         int delta; // count of bytes moved to b after an iteration
 271:         int datalen;
 272:         byte[] data;
 273:         while (len > 0)
 274:           // we need to read SASL buffers, as long as there are at least
 275:           // 4 bytes available at the source
 276:           if (source.available() > 3)
 277:             {
 278:               // process a buffer
 279:               data = readSaslBuffer();
 280:               if (data == null)
 281:                 {
 282:                   if (Configuration.DEBUG)
 283:                     log.finer("Underlying stream exhausted. Breaking...");
 284:                   break;
 285:                 }
 286:               datalen = data.length;
 287:               // copy [part of] the result to b
 288:               remaining = (datalen <= len) ? 0 : datalen - len;
 289:               delta = datalen - remaining;
 290:               System.arraycopy(data, 0, b, off, delta);
 291:               if (remaining > 0)
 292:                 {
 293:                   internalBuf = new byte[remaining];
 294:                   System.arraycopy(data, delta, internalBuf, 0, remaining);
 295:                 }
 296:               // update off, result and len
 297:               off += delta;
 298:               result += delta;
 299:               len -= delta;
 300:             }
 301:           else
 302:             { // nothing much we can do except return what we have
 303:               if (Configuration.DEBUG)
 304:                 log.finer("Not enough bytes in source to read a buffer. Breaking...");
 305:               break;
 306:             }
 307:       }
 308:     if (Configuration.DEBUG)
 309:       {
 310:         log.finer("Remaining: "
 311:                   + (internalBuf == null ? 0 : internalBuf.length));
 312:         log.exiting(this.getClass().getName(), "read()", String.valueOf(result));
 313:       }
 314:     return result;
 315:   }
 316: 
 317:   /**
 318:    * Reads a SASL buffer from the underlying source if at least 4 bytes are
 319:    * available.
 320:    *
 321:    * @return the byte[] of decoded buffer contents, or null if the underlying
 322:    *         source was exhausted.
 323:    * @throws IOException if an I/O exception occurs during the operation.
 324:    */
 325:   private byte[] readSaslBuffer() throws IOException
 326:   {
 327:     if (Configuration.DEBUG)
 328:       log.entering(this.getClass().getName(), "readSaslBuffer()");
 329:     int realLength; // check if we read as many bytes as we're supposed to
 330:     byte[] result = new byte[4];
 331:     try
 332:       {
 333:         realLength = source.read(result);
 334:         if (realLength == -1)
 335:           {
 336:             if (Configuration.DEBUG)
 337:               log.exiting(this.getClass().getName(), "readSaslBuffer");
 338:             return null;
 339:           }
 340:       }
 341:     catch (IOException x)
 342:       {
 343:         if (Configuration.DEBUG)
 344:           log.throwing(this.getClass().getName(), "readSaslBuffer", x);
 345:         throw x;
 346:       }
 347:     if (realLength != 4)
 348:       throw new IOException("Was expecting 4 but found " + realLength);
 349:     int bufferLength =  result[0]         << 24
 350:                      | (result[1] & 0xFF) << 16
 351:                      | (result[2] & 0xFF) << 8
 352:                      | (result[3] & 0xFF);
 353:     if (Configuration.DEBUG)
 354:       log.finer("SASL buffer size: " + bufferLength);
 355:     if (bufferLength > maxRawSendSize || bufferLength < 0)
 356:       throw new SaslEncodingException("SASL buffer (security layer) too long");
 357: 
 358:     result = new byte[bufferLength];
 359:     try
 360:       {
 361:         realLength = source.read(result);
 362:       }
 363:     catch (IOException x)
 364:       {
 365:         if (Configuration.DEBUG)
 366:           log.throwing(this.getClass().getName(), "readSaslBuffer", x);
 367:         throw x;
 368:       }
 369:     if (realLength != bufferLength)
 370:       throw new IOException("Was expecting " + bufferLength + " but found "
 371:                             + realLength);
 372:     if (Configuration.DEBUG)
 373:       {
 374:         log.finer("Incoming buffer (before security) (hex): "
 375:                   + Util.dumpString(result));
 376:         log.finer("Incoming buffer (before security) (str): \""
 377:                   + new String(result) + "\"");
 378:       }
 379:     if (client != null)
 380:       result = client.unwrap(result, 0, realLength);
 381:     else
 382:       result = server.unwrap(result, 0, realLength);
 383:     if (Configuration.DEBUG)
 384:       {
 385:         log.finer("Incoming buffer (after security) (hex): "
 386:                   + Util.dumpString(result));
 387:         log.finer("Incoming buffer (after security) (str): \""
 388:                   + new String(result) + "\"");
 389:         log.exiting(this.getClass().getName(), "readSaslBuffer");
 390:       }
 391:     return result;
 392:   }
 393: }