Source for gnu.javax.crypto.sasl.SaslInputStream

   1: /* SaslInputStream.java --
   2:    Copyright (C) 2003, 2006, 2010 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 = Configuration.DEBUG ?
  61:                 Logger.getLogger(SaslInputStream.class.getName()) : null;
  62:   private SaslClient client;
  63:   private SaslServer server;
  64:   private int maxRawSendSize;
  65:   private InputStream source;
  66:   private byte[] internalBuf;
  67: 
  68:   public SaslInputStream(SaslClient client, InputStream source)
  69:       throws IOException
  70:   {
  71:     super();
  72: 
  73:     this.client = client;
  74:     String size = (String) client.getNegotiatedProperty(Sasl.RAW_SEND_SIZE);
  75:     maxRawSendSize = Integer.parseInt(size);
  76:     server = null;
  77:     this.source = source;
  78:   }
  79: 
  80:   public SaslInputStream(SaslServer server, InputStream source)
  81:       throws IOException
  82:   {
  83:     super();
  84: 
  85:     this.server = server;
  86:     String size = (String) server.getNegotiatedProperty(Sasl.RAW_SEND_SIZE);
  87:     maxRawSendSize = Integer.parseInt(size);
  88:     client = null;
  89:     this.source = source;
  90:   }
  91: 
  92:   public int available() throws IOException
  93:   {
  94:     return (internalBuf == null) ? 0 : internalBuf.length;
  95:   }
  96: 
  97:   public void close() throws IOException
  98:   {
  99:     source.close();
 100:   }
 101: 
 102:   /**
 103:    * Reads the next byte of data from the input stream. The value byte is
 104:    * returned as an <code>int</code> in the range <code>0</code> to
 105:    * <code>255</code>. If no byte is available because the end of the stream
 106:    * has been reached, the value <code>-1</code> is returned. This method
 107:    * blocks until input data is available, the end of the stream is detected, or
 108:    * an exception is thrown.
 109:    * <p>
 110:    * From a SASL mechanism provider's perspective, if a security layer has been
 111:    * negotiated, the underlying <i>source</i> is expected to contain SASL
 112:    * buffers, as defined in RFC 2222. Four octets in network byte order in the
 113:    * front of each buffer identify the length of the buffer. The provider is
 114:    * responsible for performing any integrity checking or other processing on
 115:    * the buffer before returning the data as a stream of octets. For example,
 116:    * the protocol driver's request for a single octet from the stream might;
 117:    * i.e. an invocation of this method, may result in an entire SASL buffer
 118:    * being read and processed before that single octet can be returned.
 119:    *
 120:    * @return the next byte of data, or <code>-1</code> if the end of the
 121:    *         stream is reached.
 122:    * @throws IOException if an I/O error occurs.
 123:    */
 124:   public int read() throws IOException
 125:   {
 126:     int result = -1;
 127:     if (internalBuf != null && internalBuf.length > 0)
 128:       {
 129:         result = internalBuf[0] & 0xFF;
 130:         if (internalBuf.length == 1)
 131:           internalBuf = new byte[0];
 132:         else
 133:           {
 134:             byte[] tmp = new byte[internalBuf.length - 1];
 135:             System.arraycopy(internalBuf, 1, tmp, 0, tmp.length);
 136:             internalBuf = tmp;
 137:           }
 138:       }
 139:     else
 140:       {
 141:         byte[] buf = new byte[1];
 142:         int check = read(buf);
 143:         result = (check > 0) ? (buf[0] & 0xFF) : -1;
 144:       }
 145:     return result;
 146:   }
 147: 
 148:   /**
 149:    * Reads up to <code>len</code> bytes of data from the underlying <i>source</i>
 150:    * input stream into an array of bytes. An attempt is made to read as many as
 151:    * <code>len</code> bytes, but a smaller number may be read, possibly zero.
 152:    * The number of bytes actually read is returned as an integer.
 153:    * <p>
 154:    * This method blocks until input data is available, end of file is detected,
 155:    * or an exception is thrown.
 156:    * <p>
 157:    * If <code>b</code> is <code>null</code>, a {@link NullPointerException}
 158:    * is thrown.
 159:    * <p>
 160:    * If <code>off</code> is negative, or <code>len</code> is negative, or
 161:    * <code>off+len</code> is greater than the length of the array
 162:    * <code>b</code>, then an {@link IndexOutOfBoundsException} is thrown.
 163:    * <p>
 164:    * If <code>len</code> is zero, then no bytes are read and <code>0</code>
 165:    * is returned; otherwise, there is an attempt to read at least one byte. If
 166:    * no byte is available because the stream is at end of file, the value
 167:    * <code>-1</code> is returned; otherwise, at least one byte is read and
 168:    * stored into <code>b</code>.
 169:    * <p>
 170:    * The first byte read is stored into element <code>b[off]</code>, the next
 171:    * one into <code>b[off+1]</code>, and so on. The number of bytes read is,
 172:    * at most, equal to <code>len</code>. Let <code>k</code> be the number
 173:    * of bytes actually read; these bytes will be stored in elements
 174:    * <code>b[off]</code> through <code>b[off+k-1]</code>, leaving elements
 175:    * <code>b[off+k]</code> through <code>b[off+len-1]</code> unaffected.
 176:    * <p>
 177:    * In every case, elements <code>b[0]</code> through <code>b[off]</code>
 178:    * and elements <code>b[off+len]</code> through <code>b[b.length-1]</code>
 179:    * are unaffected.
 180:    * <p>
 181:    * If the first byte cannot be read for any reason other than end of file,
 182:    * then an {@link IOException} is thrown. In particular, an
 183:    * {@link IOException} is thrown if the input stream has been closed.
 184:    * <p>
 185:    * From the SASL mechanism provider's perspective, if a security layer has
 186:    * been negotiated, the underlying <i>source</i> is expected to contain SASL
 187:    * buffers, as defined in RFC 2222. Four octets in network byte order in the
 188:    * front of each buffer identify the length of the buffer. The provider is
 189:    * responsible for performing any integrity checking or other processing on
 190:    * the buffer before returning the data as a stream of octets. The protocol
 191:    * driver's request for a single octet from the stream might result in an
 192:    * entire SASL buffer being read and processed before that single octet can be
 193:    * returned.
 194:    *
 195:    * @param b the buffer into which the data is read.
 196:    * @param off the start offset in array <code>b</code> at which the data is
 197:    *          wricodeen.
 198:    * @param len the maximum number of bytes to read.
 199:    * @return the total number of bytes read into the buffer, or <code>-1</code>
 200:    *         if there is no more data because the end of the stream has been
 201:    *         reached.
 202:    * @throws IOException if an I/O error occurs.
 203:    */
 204:   public int read(byte[] b, int off, int len) throws IOException
 205:   {
 206:     if (Configuration.DEBUG)
 207:       log.entering(this.getClass().getName(), "read", new Object[] {
 208:           b, Integer.valueOf(off), Integer.valueOf(len)
 209:       });
 210:     if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length)
 211:         || ((off + len) < 0))
 212:       throw new IndexOutOfBoundsException("off=" + off + ", len=" + len
 213:                                           + ", b.length=" + b.length);
 214:     if (len == 0)
 215:       {
 216:         if (Configuration.DEBUG)
 217:           log.exiting(this.getClass().getName(), "read", Integer.valueOf(0));
 218:         return 0;
 219:       }
 220:     if (Configuration.DEBUG)
 221:       log.finer("Available: " + available());
 222:     int result = 0;
 223:     if (internalBuf == null || internalBuf.length < 1)
 224:       try
 225:         {
 226:           internalBuf = readSaslBuffer();
 227:           if (internalBuf == null)
 228:             {
 229:               if (Configuration.DEBUG)
 230:                 {
 231:                   log.finer("Underlying stream empty. Returning -1");
 232:                   log.exiting(this.getClass().getName(), "read",
 233:                               Integer.valueOf(-1));
 234:                 }
 235:               return -1;
 236:             }
 237:         }
 238:       catch (InterruptedIOException x)
 239:         {
 240:           if (Configuration.DEBUG)
 241:             {
 242:               log.finer("Reading thread was interrupted. Returning -1");
 243:               log.throwing(this.getClass().getName(), "read", x);
 244:               log.exiting(this.getClass().getName(), "read",
 245:                           Integer.valueOf(-1));
 246:             }
 247:           return -1;
 248:         }
 249:     if (len <= internalBuf.length)
 250:       {
 251:         result = len;
 252:         System.arraycopy(internalBuf, 0, b, off, len);
 253:         if (len == internalBuf.length)
 254:           internalBuf = null;
 255:         else
 256:           {
 257:             byte[] tmp = new byte[internalBuf.length - len];
 258:             System.arraycopy(internalBuf, len, tmp, 0, tmp.length);
 259:             internalBuf = tmp;
 260:           }
 261:       }
 262:     else
 263:       {
 264:         // first copy the available bytes to b
 265:         result = internalBuf.length;
 266:         System.arraycopy(internalBuf, 0, b, off, result);
 267:         internalBuf = null;
 268:         off += result;
 269:         len -= result;
 270:         int remaining; // count of bytes remaining in buffer after an iteration
 271:         int delta; // count of bytes moved to b after an iteration
 272:         int datalen;
 273:         byte[] data;
 274:         while (len > 0)
 275:           // we need to read SASL buffers, as long as there are at least
 276:           // 4 bytes available at the source
 277:           if (source.available() > 3)
 278:             {
 279:               // process a buffer
 280:               data = readSaslBuffer();
 281:               if (data == null)
 282:                 {
 283:                   if (Configuration.DEBUG)
 284:                     log.finer("Underlying stream exhausted. Breaking...");
 285:                   break;
 286:                 }
 287:               datalen = data.length;
 288:               // copy [part of] the result to b
 289:               remaining = (datalen <= len) ? 0 : datalen - len;
 290:               delta = datalen - remaining;
 291:               System.arraycopy(data, 0, b, off, delta);
 292:               if (remaining > 0)
 293:                 {
 294:                   internalBuf = new byte[remaining];
 295:                   System.arraycopy(data, delta, internalBuf, 0, remaining);
 296:                 }
 297:               // update off, result and len
 298:               off += delta;
 299:               result += delta;
 300:               len -= delta;
 301:             }
 302:           else
 303:             { // nothing much we can do except return what we have
 304:               if (Configuration.DEBUG)
 305:                 log.finer("Not enough bytes in source to read a buffer. Breaking...");
 306:               break;
 307:             }
 308:       }
 309:     if (Configuration.DEBUG)
 310:       {
 311:         log.finer("Remaining: "
 312:                   + (internalBuf == null ? 0 : internalBuf.length));
 313:         log.exiting(this.getClass().getName(), "read()", String.valueOf(result));
 314:       }
 315:     return result;
 316:   }
 317: 
 318:   /**
 319:    * Reads a SASL buffer from the underlying source if at least 4 bytes are
 320:    * available.
 321:    *
 322:    * @return the byte[] of decoded buffer contents, or null if the underlying
 323:    *         source was exhausted.
 324:    * @throws IOException if an I/O exception occurs during the operation.
 325:    */
 326:   private byte[] readSaslBuffer() throws IOException
 327:   {
 328:     if (Configuration.DEBUG)
 329:       log.entering(this.getClass().getName(), "readSaslBuffer()");
 330:     int realLength; // check if we read as many bytes as we're supposed to
 331:     byte[] result = new byte[4];
 332:     try
 333:       {
 334:         realLength = source.read(result);
 335:         if (realLength == -1)
 336:           {
 337:             if (Configuration.DEBUG)
 338:               log.exiting(this.getClass().getName(), "readSaslBuffer");
 339:             return null;
 340:           }
 341:       }
 342:     catch (IOException x)
 343:       {
 344:         if (Configuration.DEBUG)
 345:           log.throwing(this.getClass().getName(), "readSaslBuffer", x);
 346:         throw x;
 347:       }
 348:     if (realLength != 4)
 349:       throw new IOException("Was expecting 4 but found " + realLength);
 350:     int bufferLength =  result[0]         << 24
 351:                      | (result[1] & 0xFF) << 16
 352:                      | (result[2] & 0xFF) << 8
 353:                      | (result[3] & 0xFF);
 354:     if (Configuration.DEBUG)
 355:       log.finer("SASL buffer size: " + bufferLength);
 356:     if (bufferLength > maxRawSendSize || bufferLength < 0)
 357:       throw new SaslEncodingException("SASL buffer (security layer) too long");
 358: 
 359:     result = new byte[bufferLength];
 360:     try
 361:       {
 362:         realLength = source.read(result);
 363:       }
 364:     catch (IOException x)
 365:       {
 366:         if (Configuration.DEBUG)
 367:           log.throwing(this.getClass().getName(), "readSaslBuffer", x);
 368:         throw x;
 369:       }
 370:     if (realLength != bufferLength)
 371:       throw new IOException("Was expecting " + bufferLength + " but found "
 372:                             + realLength);
 373:     if (Configuration.DEBUG)
 374:       {
 375:         log.finer("Incoming buffer (before security) (hex): "
 376:                   + Util.dumpString(result));
 377:         log.finer("Incoming buffer (before security) (str): \""
 378:                   + new String(result) + "\"");
 379:       }
 380:     if (client != null)
 381:       result = client.unwrap(result, 0, realLength);
 382:     else
 383:       result = server.unwrap(result, 0, realLength);
 384:     if (Configuration.DEBUG)
 385:       {
 386:         log.finer("Incoming buffer (after security) (hex): "
 387:                   + Util.dumpString(result));
 388:         log.finer("Incoming buffer (after security) (str): \""
 389:                   + new String(result) + "\"");
 390:         log.exiting(this.getClass().getName(), "readSaslBuffer");
 391:       }
 392:     return result;
 393:   }
 394: }