// jTDS JDBC Driver for Microsoft SQL Server and Sybase
//Copyright (C) 2004 The jTDS Project
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.
//
//This library 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
//Lesser General Public License for more details.
//
//You should have received a copy of the GNU Lesser General Public
//License along with this library; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
package net.sourceforge.jtds.ssl;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.jtds.jdbc.TdsCore;
/**
* An output stream that mediates between JSSE and the DB server.
* <p/>
* SQL Server 2000 has the following requirements:
* <ul>
* <li>All handshake records are delivered in TDS packets.
* <li>The "Client Key Exchange" (CKE), "Change Cipher Spec" (CCS) and
* "Finished" (FIN) messages are to be submitted in the delivered in both
* the same TDS packet and the same TCP packet.
* <li>From then on TLS/SSL records should be transmitted as normal -- the
* TDS packet is part of the encrypted application data.
*
* @author Rob Worsnop
* @author Mike Hutchinson
* @version $Id: TdsTlsOutputStream.java,v 1.4 2005-04-28 14:29:31 alin_sinpalean Exp $
*/
class TdsTlsOutputStream extends FilterOutputStream {
/**
* Used for holding back CKE, CCS and FIN records.
*/
final private List bufferedRecords = new ArrayList();
private int totalSize;
/**
* Constructs a TdsTlsOutputStream based on an underlying output stream.
*
* @param out the underlying output stream
*/
TdsTlsOutputStream(OutputStream out) {
super(out);
}
/**
* Holds back a record for batched transmission.
*
* @param record the TLS record to buffer
* @param len the length of the TLS record to buffer
*/
private void deferRecord(byte record[], int len) {
byte tmp[] = new byte[len];
System.arraycopy(record, 0, tmp, 0, len);
bufferedRecords.add(tmp);
totalSize += len;
}
/**
* Transmits the buffered batch of records.
*/
private void flushBufferedRecords() throws IOException {
byte tmp[] = new byte[totalSize];
int off = 0;
for (int i = 0; i < bufferedRecords.size(); i++) {
byte x[] = (byte[])bufferedRecords.get(i);
System.arraycopy(x, 0, tmp, off, x.length);
off += x.length;
}
putTdsPacket(tmp, off);
bufferedRecords.clear();
totalSize = 0;
}
public void write(byte[] b, int off, int len) throws IOException {
if (len < Ssl.TLS_HEADER_SIZE || off > 0) {
// Too short for a TLS packet just write it
out.write(b, off, len);
return;
}
//
// Extract relevant TLS header fields
//
int contentType = b[0] & 0xFF;
int length = ((b[3] & 0xFF) << 8) | (b[4] & 0xFF);
//
// Check to see if probably a SSL client hello
//
if (contentType < Ssl.TYPE_CHANGECIPHERSPEC ||
contentType > Ssl.TYPE_APPLICATIONDATA ||
length != len - Ssl.TLS_HEADER_SIZE) {
// Assume SSLV2 Client Hello
putTdsPacket(b, len);
return;
}
//
// Process TLS records
//
switch (contentType) {
case Ssl.TYPE_APPLICATIONDATA:
// Application data, just copy to output
out.write(b, off, len);
break;
case Ssl.TYPE_CHANGECIPHERSPEC:
// Cipher spec change has to be buffered
deferRecord(b, len);
break;
case Ssl.TYPE_ALERT:
// Alert record ignore!
break;
case Ssl.TYPE_HANDSHAKE:
// TLS Handshake records
if (len >= (Ssl.TLS_HEADER_SIZE + Ssl.HS_HEADER_SIZE)) {
// Long enough for a handshake subheader
int hsType = b[5];
int hsLen = (b[6] & 0xFF) << 16 |
(b[7] & 0xFF) << 8 |
(b[8] & 0xFF);
if (hsLen == len - (Ssl.TLS_HEADER_SIZE + Ssl.HS_HEADER_SIZE) &&
// Client hello has to go in its own TDS packet
hsType == Ssl.TYPE_CLIENTHELLO) {
putTdsPacket(b, len);
break;
}
// All others have to be deferred and sent as a block
deferRecord(b, len);
//
// Now see if we have a finish record which will flush the
// buffered records.
//
if (hsLen != len - (Ssl.TLS_HEADER_SIZE + Ssl.HS_HEADER_SIZE) ||
hsType != Ssl.TYPE_CLIENTKEYEXCHANGE) {
// This is probably a finish record
flushBufferedRecords();
}
break;
}
default:
// Short or unknown record output it anyway
out.write(b, off, len);
break;
}
}
/**
* Write a TDS packet containing the TLS record(s).
*
* @param b the TLS record
* @param len the length of the TLS record
*/
void putTdsPacket(byte[] b, int len) throws IOException {
byte tdsHdr[] = new byte[TdsCore.PKT_HDR_LEN];
tdsHdr[0] = TdsCore.PRELOGIN_PKT;
tdsHdr[1] = 0x01;
tdsHdr[2] = (byte)((len + TdsCore.PKT_HDR_LEN) >> 8);
tdsHdr[3] = (byte)(len + TdsCore.PKT_HDR_LEN);
out.write(tdsHdr, 0, tdsHdr.length);
out.write(b, 0, len);
}
/*
* (non-Javadoc)
*
* @see java.io.OutputStream#flush()
*/
public void flush() throws IOException {
super.flush();
}
}