/* * 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 Sun Microsystems, Inc. */ package org.opends.server.extensions; import java.nio.channels.ByteChannel; import java.security.cert.Certificate; import static org.opends.server.loggers.debug.DebugLogger.*; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SocketChannel; import javax.security.sasl.Sasl; import org.opends.server.api.ClientConnection; import org.opends.server.loggers.debug.DebugTracer; import org.opends.server.protocols.ldap.LDAPClientConnection; import org.opends.server.util.StaticUtils; /** * This class implements a SASL byte channel that can be used during * confidentiality and integrity. * */ public class SASLByteChannel implements ByteChannel, ConnectionSecurityProvider { // The tracer object for the debug logger. private static final DebugTracer TRACER = getTracer(); // The client connection associated with this provider. private ClientConnection connection; // The socket channel associated with this provider. private SocketChannel sockChannel; // The SASL context associated with the provider private SASLContext saslContext; // The number of bytes in the length buffer. private final int lengthSize = 4; // A byte buffer used to hold the length of the clear buffer. private ByteBuffer lengthBuf = ByteBuffer.allocate(lengthSize); // The SASL mechanism name. private String name; /** * 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.sockChannel = ((LDAPClientConnection) connection).getSocketChannel(); } /** * 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 associaetd with the instance. * @return A SASL byte channel. */ public static SASLByteChannel getSASLByteChannel(ClientConnection c, String name, SASLContext context) { return new SASLByteChannel(c, name, context); } /** * Read from the socket channel into the specified byte buffer 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 (sockChannel.isOpen() && total > 0) { count = sockChannel.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; byte[] buf = byteBuf.array(); for (int i = 0; i < lengthSize; i++) { answer <<= 8; answer |= ((int) buf[i] & 0xff); } return answer; } /** * {@inheritDoc} */ public int read(ByteBuffer clearDst) throws IOException { int recvBufSize = getAppBufSize(); if(recvBufSize > clearDst.capacity()) return -1; lengthBuf.clear(); int readResult = readAll(lengthBuf, lengthSize); if (readResult == -1) return -1; else if (readResult == 0) return 0; int bufLength = getBufLength(lengthBuf); if (bufLength > recvBufSize) //TODO SASLPhase2 add message return -1; ByteBuffer readBuf = ByteBuffer.allocate(bufLength); readResult = readAll(readBuf, bufLength); if (readResult == -1) return -1; else if (readResult == 0) return 0; byte[] inBytes = readBuf.array(); byte[] clearBytes = saslContext.unwrap(inBytes, 0, inBytes.length); for(int i = 0; i < clearBytes.length; i++) { clearDst.put(clearBytes[i]); } return clearDst.remaining(); } /** * 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 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 { int bytesWritten = sockChannel.write(buffer); if (bytesWritten < 0) throw new ClosedChannelException(); else if (bytesWritten == 0) { if(!StaticUtils.writeWithTimeout( connection, sockChannel, buffer)) throw new ClosedChannelException(); } return bytesWritten; } /** * {@inheritDoc} */ public void close() throws IOException { saslContext.dispose(); saslContext=null; } /** * {@inheritDoc} */ public boolean isOpen() { return saslContext != null; } /** * {@inheritDoc} */ public int getAppBufSize() { return saslContext.getBufSize(Sasl.RAW_SEND_SIZE) + lengthSize; } /** * {@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; } }