/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2008-2009 Sun Microsystems, Inc. */ package org.opends.server.extensions; import java.nio.channels.ByteChannel; import java.security.cert.Certificate; import java.io.IOException; import java.nio.ByteBuffer; import javax.security.sasl.Sasl; import org.opends.server.api.ClientConnection; /** * This class implements a SASL byte channel that can be used during * confidentiality and integrity. * */ public class SASLByteChannel implements ByteChannel, ConnectionSecurityProvider { // The client connection associated with this provider. private ClientConnection connection; // The SASL context associated with the provider private SASLContext saslContext; // The byte channel associated with this provider. private RedirectingByteChannel channel; // The number of bytes in the length buffer. private final int lengthSize = 4; //Length of the buffer. private int bufLength; // The SASL mechanism name. private String name; //Buffers used in reading and decoding (unwrap) private ByteBuffer readBuffer, decodeBuffer; //How many bytes of the subsequent buffer is needed to complete a partially //read buffer. private int neededBytes = 0; //Used to not reset the buffer length size because the first 4 bytes of a //buffer are not size bytes. private boolean reading = false; /** * Create a SASL byte channel with the specified parameters * that is capable of processing a confidentiality/integrity SASL * connection. * * @param connection * The client connection to read/write the bytes. * @param name * The SASL mechanism name. * @param saslContext * The SASL context to process the data through. */ private SASLByteChannel(ClientConnection connection, String name, SASLContext saslContext) { this.connection = connection; this.name = name; this.saslContext = saslContext; this.channel = connection.getChannel(); this.readBuffer = ByteBuffer.allocate(connection.getAppBufferSize()); this.decodeBuffer = ByteBuffer.allocate(connection.getAppBufferSize() + lengthSize); } /** * Return a SASL byte channel instance created using the specified * parameters. * * @param c A client connection associated with the instance. * @param name The name of the instance (SASL mechanism name). * @param context A SASL context associated with the instance. * @return A SASL byte channel. */ public static SASLByteChannel getSASLByteChannel(ClientConnection c, String name, SASLContext context) { return new SASLByteChannel(c, name, context); } /** * Finish processing a previous, partially read buffer using some, or, all * of the bytes of the current buffer. * */ private int processPartial(int readResult, ByteBuffer clearDst) throws IOException { readBuffer.flip(); //Use all of the bytes of the current buffer and read some more. if(neededBytes > readResult) { neededBytes -= readResult; decodeBuffer.put(readBuffer); readBuffer.clear(); reading = false; return 0; } //Use a portion of the current buffer. for(;neededBytes > 0;neededBytes--) { decodeBuffer.put(readBuffer.get()); } //Unwrap the now completed buffer. byte[] inBytes = decodeBuffer.array(); byte[]clearBytes = saslContext.unwrap(inBytes, lengthSize, bufLength); clearDst.put(clearBytes); decodeBuffer.clear(); readBuffer.compact(); //If the read buffer has bytes, these are a new buffer. Reset the //buffer length to the new value. if(readBuffer.position() != 0) { bufLength = getBufLength(readBuffer); reading = true; } else reading=false; return clearDst.position(); } /** * Read from the socket channel into the specified byte buffer at least * the number of bytes specified in the total parameter. * * @param byteBuf * The byte buffer to put the bytes in. * @param total * The total number of bytes to read from the socket * channel. * @return The number of bytes read, 0 or -1. * @throws IOException * If an error occurred reading the socket channel. */ private int readAll(ByteBuffer byteBuf, int total) throws IOException { int count = 0; while (channel.isOpen() && total > 0) { count = channel.read(byteBuf); if (count == -1) return -1; if (count == 0) return 0; total -= count; } if (total > 0) return -1; else return byteBuf.position(); } /** * Return the clear buffer length as determined by processing the * first 4 bytes of the specified buffer. * * @param byteBuf * The buffer to examine the first 4 bytes of. * @return The size of the clear buffer. */ private int getBufLength(ByteBuffer byteBuf) { int answer = 0; for (int i = 0; i < lengthSize; i++) { byte b = byteBuf.get(i); answer <<= 8; answer |= ((int) b & 0xff); } return answer; } /** * {@inheritDoc} */ public synchronized int read(ByteBuffer clearDst) throws IOException { int bytesToRead = lengthSize; if(reading) bytesToRead = neededBytes; int readResult = readAll(readBuffer, bytesToRead); if (readResult == -1) return -1; //The previous buffer read was not complete, the current //buffer completes it. if(neededBytes > 0 && readResult > 0) return(processPartial(readResult, clearDst)); if(readResult == 0 && !reading) return 0; if(!reading) { bufLength = getBufLength(readBuffer); } reading=false; //The buffer length is greater than what is there, save what is there, //figure out how much more is needed and return. if(bufLength > readBuffer.position()) { neededBytes = bufLength - readBuffer.position() + lengthSize; readBuffer.flip(); decodeBuffer.put(readBuffer); readBuffer.clear(); return 0; } else { readBuffer.flip(); decodeBuffer.put(readBuffer); byte[] inBytes = decodeBuffer.array(); byte[]clearBytes = saslContext.unwrap(inBytes, lengthSize, bufLength); decodeBuffer.clear(); clearDst.put(clearBytes); readBuffer.clear(); } return clearDst.position(); } /** * Writes the specified len parameter into the buffer in a form that * can be sent over a network to the client. * * @param buf * The buffer to hold the length bytes. * @param len * The length to encode. */ private void writeBufLen(byte[] buf, int len) { for (int i = 3; i >= 0; i--) { buf[i] = (byte) (len & 0xff); len >>>= 8; } } /** * Creates a buffer suitable to send to the client using the * specified clear byte array and length of the bytes to * wrap. * * @param clearBytes * The clear byte array to send to the client. * @param len * The length of the bytes to wrap in the byte array. * @throws SaslException * If the wrap of the bytes fails. */ private ByteBuffer wrap(byte[] clearBytes, int len) throws IOException { byte[] wrapBytes = saslContext.wrap(clearBytes, 0, len); byte[] outBytes = new byte[wrapBytes.length + lengthSize]; writeBufLen(outBytes, wrapBytes.length); System.arraycopy(wrapBytes, 0, outBytes, lengthSize, wrapBytes.length); return ByteBuffer.wrap(outBytes); } /** * {@inheritDoc} */ public synchronized int write(ByteBuffer clearSrc) throws IOException { int sendBufSize = getAppBufSize(); int srcLen = clearSrc.remaining(); ByteBuffer sendBuffer = ByteBuffer.allocate(sendBufSize); if (srcLen > sendBufSize) { int oldPos = clearSrc.position(); int curPos = oldPos; int curLimit = oldPos + sendBufSize; while (curPos < srcLen) { clearSrc.position(curPos); clearSrc.limit(curLimit); sendBuffer.put(clearSrc); writeChannel(wrap(sendBuffer.array(), clearSrc.remaining())); curPos = curLimit; curLimit = Math.min(srcLen, curPos + sendBufSize); } return srcLen; } else { sendBuffer.put(clearSrc); return writeChannel(wrap(sendBuffer.array() ,srcLen)); } } /** * Write the specified byte buffer to the socket channel. * * @param buffer * The byte buffer to write to the socket channel. * @return {@code true} if the byte buffer was successfully written * to the socket channel, or, {@code false} if not. */ private int writeChannel(ByteBuffer buffer) throws IOException { return channel.write(buffer); } /** * {@inheritDoc} */ public synchronized void close() throws IOException { saslContext.dispose(); saslContext=null; } /** * {@inheritDoc} */ public boolean isOpen() { return saslContext != null; } /** * {@inheritDoc} */ public int getAppBufSize() { return saslContext.getBufSize(Sasl.MAX_BUFFER); } /** * {@inheritDoc} */ public Certificate[] getClientCertificateChain() { return new Certificate[0]; } /** * {@inheritDoc} */ public int getSSF() { return saslContext.getSSF(); } /** * {@inheritDoc} */ public ByteChannel wrapChannel(ByteChannel channel) { return this; } /** * {@inheritDoc} */ public String getName() { return name; } /** * {@inheritDoc} */ public boolean isSecure() { return true; } }