/* * Copyright (C) 2006-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.jlan.smb.server; import java.io.IOException; import org.alfresco.jlan.debug.Debug; import org.alfresco.jlan.netbios.RFCNetBIOSProtocol; import org.alfresco.jlan.server.filesys.TreeConnection; import org.alfresco.jlan.smb.DataType; import org.alfresco.jlan.smb.PacketType; import org.alfresco.jlan.smb.SMBStatus; import org.alfresco.jlan.smb.TransactBuffer; import org.alfresco.jlan.smb.dcerpc.DCEBuffer; import org.alfresco.jlan.smb.dcerpc.DCEBufferException; import org.alfresco.jlan.smb.dcerpc.DCECommand; import org.alfresco.jlan.smb.dcerpc.DCEDataPacker; import org.alfresco.jlan.smb.dcerpc.DCEPipeType; import org.alfresco.jlan.smb.dcerpc.UUID; import org.alfresco.jlan.smb.dcerpc.server.DCEPipeFile; import org.alfresco.jlan.smb.dcerpc.server.DCESrvPacket; import org.alfresco.jlan.util.DataBuffer; import org.alfresco.jlan.util.DataPacker; /** * DCE/RPC Protocol Handler Class * * @author gkspencer */ public class DCERPCHandler { /** * Process a DCE/RPC request * * @param sess SMBSrvSession * @param srvTrans SMBSrvTransPacket * @param smbPkt SMBSrvPacket * @exception IOException * @exception SMBSrvException */ public static final void processDCERPCRequest(SMBSrvSession sess, SMBSrvTransPacket srvTrans, SMBSrvPacket smbPkt) throws IOException, SMBSrvException { // Get the tree id from the received packet and validate that it is a valid // connection id. TreeConnection conn = sess.findTreeConnection(srvTrans); if ( conn == null) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); return; } // Get the file id and validate int fid = srvTrans.getSetupParameter(1); int maxData = srvTrans.getParameter(3) - DCEBuffer.OPERATIONDATA; // Get the IPC pipe file for the specified file id DCEPipeFile pipeFile = (DCEPipeFile) conn.findFile(fid); if ( pipeFile == null) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos); return; } // Create a DCE/RPC buffer from the received data DCEBuffer dceBuf = new DCEBuffer(srvTrans.getBuffer(), srvTrans.getParameter(10) + RFCNetBIOSProtocol.HEADER_LEN); // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_DCERPC)) sess.debugPrintln("TransactNmPipe pipeFile=" + pipeFile.getName() + ", fid=" + fid + ", dceCmd=0x" + Integer.toHexString(dceBuf.getHeaderValue(DCEBuffer.HDR_PDUTYPE))); // Process the received DCE buffer processDCEBuffer(sess, dceBuf, pipeFile, smbPkt); // Check if there is a reply buffer to return to the caller if ( pipeFile.hasBufferedData() == false) return; DCEBuffer txBuf = pipeFile.getBufferedData(); // Initialize the reply DCESrvPacket dcePkt = new DCESrvPacket(smbPkt.getBuffer()); // Always only one fragment as the data either fits into the first reply fragment or the // client will read the remaining data by issuing read requests on the pipe int flags = DCESrvPacket.FLG_ONLYFRAG; dcePkt.initializeDCEReply(); txBuf.setHeaderValue(DCEBuffer.HDR_FLAGS, flags); // Build the reply data byte[] buf = dcePkt.getBuffer(); int pos = DCEDataPacker.longwordAlign(dcePkt.getByteOffset()); // Set the DCE fragment size and send the reply DCE/RPC SMB int dataLen = txBuf.getLength(); txBuf.setHeaderValue(DCEBuffer.HDR_FRAGLEN, dataLen); // Copy the data from the DCE output buffer to the reply SMB packet int len = txBuf.getLength(); int sts = SMBStatus.NTSuccess; if ( len > maxData) { // Write the maximum transmit fragment to the reply len = maxData + DCEBuffer.OPERATIONDATA; dataLen = maxData + DCEBuffer.OPERATIONDATA; // Indicate a buffer overflow status sts = SMBStatus.NTBufferOverflow; } else { // Clear the DCE/RPC pipe buffered data, the reply will fit into a single response // packet pipeFile.setBufferedData(null); } // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_DCERPC)) sess.debugPrintln("Reply DCEbuf flags=0x" + Integer.toHexString(flags) + ", len=" + len + ", status=0x" + Integer.toHexString(sts)); // Copy the reply data to the reply packet try { pos += txBuf.copyData(buf, pos, len); } catch (DCEBufferException ex) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv); return; } // Set the SMB transaction data length int byteLen = pos - dcePkt.getByteOffset(); dcePkt.setParameter(1, dataLen); dcePkt.setParameter(6, dataLen); dcePkt.setByteCount(byteLen); dcePkt.setFlags2(SMBPacket.FLG2_LONGERRORCODE); dcePkt.setLongErrorCode(sts); sess.sendResponseSMB(dcePkt); } /** * Process a DCE/RPC request * * @param sess SMBSrvSession * @param vc VirtualCircuit * @param tbuf TransactBuffer * @param smbPkt SMBSrvPacket * @exception IOException * @exception SMBSrvException */ public static final void processDCERPCRequest(SMBSrvSession sess, VirtualCircuit vc, TransactBuffer tbuf, SMBSrvPacket smbPkt) throws IOException, SMBSrvException { // Check if the transaction buffer has setup and data buffers if ( tbuf.hasSetupBuffer() == false || tbuf.hasDataBuffer() == false) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVUnrecognizedCommand, SMBStatus.ErrSrv); return; } // Get the tree id from the received packet and validate that it is a valid // connection id. int treeId = tbuf.getTreeId(); TreeConnection conn = vc.findConnection(treeId); if ( conn == null) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); return; } // Get the file id and validate DataBuffer setupBuf = tbuf.getSetupBuffer(); setupBuf.skipBytes(2); int fid = setupBuf.getShort(); int maxData = tbuf.getReturnDataLimit() - DCEBuffer.OPERATIONDATA; // Get the IPC pipe file for the specified file id DCEPipeFile pipeFile = (DCEPipeFile) conn.findFile(fid); if ( pipeFile == null) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos); return; } // Create a DCE/RPC buffer from the received transaction data DCEBuffer dceBuf = new DCEBuffer(tbuf); // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_DCERPC)) sess.debugPrintln("TransactNmPipe pipeFile=" + pipeFile.getName() + ", fid=" + fid + ", dceCmd=0x" + Integer.toHexString(dceBuf.getHeaderValue(DCEBuffer.HDR_PDUTYPE))); // Process the received DCE buffer processDCEBuffer(sess, dceBuf, pipeFile, smbPkt); // Check if there is a reply buffer to return to the caller if ( pipeFile.hasBufferedData() == false) return; DCEBuffer txBuf = pipeFile.getBufferedData(); // Initialize the reply DCESrvPacket dcePkt = new DCESrvPacket(smbPkt.getBuffer()); // Always only one fragment as the data either fits into the first reply fragment or the // client will read the remaining data by issuing read requests on the pipe int flags = DCESrvPacket.FLG_ONLYFRAG; dcePkt.initializeDCEReply(); txBuf.setHeaderValue(DCEBuffer.HDR_FLAGS, flags); // Build the reply data byte[] buf = dcePkt.getBuffer(); int pos = DCEDataPacker.longwordAlign(dcePkt.getByteOffset()); // Set the DCE fragment size and send the reply DCE/RPC SMB int dataLen = txBuf.getLength(); txBuf.setHeaderValue(DCEBuffer.HDR_FRAGLEN, dataLen); // Copy the data from the DCE output buffer to the reply SMB packet int len = txBuf.getLength(); int sts = SMBStatus.NTSuccess; if ( len > maxData) { // Write the maximum transmit fragment to the reply len = maxData + DCEBuffer.OPERATIONDATA; dataLen = maxData + DCEBuffer.OPERATIONDATA; // Indicate a buffer overflow status sts = SMBStatus.NTBufferOverflow; } else { // Clear the DCE/RPC pipe buffered data, the reply will fit into a single response // packet pipeFile.setBufferedData(null); } // Check if a new buffer needs to be allocated for the response int pktLen = dcePkt.getByteOffset() + len + 4; // allow for alignment if ( smbPkt.getBufferLength() < pktLen) { // Allocate a new buffer for the response SMBSrvPacket respPkt = sess.getPacketPool().allocatePacket( pktLen, smbPkt, dcePkt.getByteOffset()); // Switch the response to the new buffer buf = respPkt.getBuffer(); dcePkt.setBuffer( buf); } // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_DCERPC)) sess.debugPrintln("Reply DCEbuf flags=0x" + Integer.toHexString(flags) + ", len=" + len + ", status=0x" + Integer.toHexString(sts)); // Copy the reply data to the reply packet try { pos += txBuf.copyData(buf, pos, len); } catch (DCEBufferException ex) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv); return; } // Set the SMB transaction data length int byteLen = pos - dcePkt.getByteOffset(); dcePkt.setParameter(1, dataLen); dcePkt.setParameter(6, dataLen); dcePkt.setByteCount(byteLen); dcePkt.setFlags2(SMBPacket.FLG2_LONGERRORCODE); dcePkt.setLongErrorCode(sts); sess.sendResponseSMB(dcePkt); } /** * Process a DCE/RPC write request to the named pipe file * * @param sess SMBSrvSession * @param smbPkt SMBSrvPacket * @exception IOException * @exception SMBSrvException */ public static final void processDCERPCRequest(SMBSrvSession sess, SMBSrvPacket smbPkt) throws IOException, SMBSrvException { // Get the tree id from the received packet and validate that it is a valid // connection id. TreeConnection conn = sess.findTreeConnection(smbPkt); if ( conn == null) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); return; } // Determine if this is a write or write andX request int cmd = smbPkt.getCommand(); // Get the file id and validate int fid = -1; if ( cmd == PacketType.WriteFile) fid = smbPkt.getParameter(0); else fid = smbPkt.getParameter(2); // Get the IPC pipe file for the specified file id DCEPipeFile pipeFile = (DCEPipeFile) conn.findFile(fid); if ( pipeFile == null) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos); return; } // Create a DCE buffer for the received data DCEBuffer dceBuf = null; byte[] buf = smbPkt.getBuffer(); int pos = 0; int len = 0; if ( cmd == PacketType.WriteFile) { // Get the data offset pos = smbPkt.getByteOffset(); // Check that the received data is valid if ( buf[pos++] != DataType.DataBlock) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos); return; } len = DataPacker.getIntelShort(buf, pos); pos += 2; } else { // Get the data offset and length len = smbPkt.getParameter(10); pos = smbPkt.getParameter(11) + RFCNetBIOSProtocol.HEADER_LEN; } // Create a DCE buffer mapped to the received packet dceBuf = new DCEBuffer(buf, pos); // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_IPC)) sess.debugPrintln("Write pipeFile=" + pipeFile.getName() + ", fid=" + fid + ", dceCmd=0x" + Integer.toHexString(dceBuf.getHeaderValue(DCEBuffer.HDR_PDUTYPE))); // Process the DCE buffer processDCEBuffer(sess, dceBuf, pipeFile, smbPkt); // Check if there is a valid reply buffered int bufLen = 0; if ( pipeFile.hasBufferedData()) bufLen = pipeFile.getBufferedData().getLength(); // Send the write/write andX reply if ( cmd == PacketType.WriteFile) { // Build the write file reply smbPkt.setParameterCount(1); smbPkt.setParameter(0, len); smbPkt.setByteCount(0); } else { // Build the write andX reply smbPkt.setParameterCount(6); smbPkt.setAndXCommand(0xFF); smbPkt.setParameter(1, 0); smbPkt.setParameter(2, len); smbPkt.setParameter(3, bufLen); smbPkt.setParameter(4, 0); smbPkt.setParameter(5, 0); smbPkt.setByteCount(0); } // Send the write reply smbPkt.setFlags2(SMBPacket.FLG2_LONGERRORCODE); sess.sendResponseSMB(smbPkt); } /** * Process a DCE/RPC pipe read request * * @param sess SMBSrvSession * @param smbPkt SMBSrvPacket * @exception IOException * @exception SMBSrvException */ public static final void processDCERPCRead(SMBSrvSession sess, SMBSrvPacket smbPkt) throws IOException, SMBSrvException { // Get the tree id from the received packet and validate that it is a valid // connection id. TreeConnection conn = sess.findTreeConnection(smbPkt); if ( conn == null) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); return; } // Determine if this is a read or read andX request int cmd = smbPkt.getCommand(); // Get the file id and read length, and validate int fid = -1; int rdLen = -1; if ( cmd == PacketType.ReadFile) { fid = smbPkt.getParameter(0); rdLen = smbPkt.getParameter(1); } else { fid = smbPkt.getParameter(2); rdLen = smbPkt.getParameter(5); } // Get the IPC pipe file for the specified file id DCEPipeFile pipeFile = (DCEPipeFile) conn.findFile(fid); if ( pipeFile == null) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos); return; } // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_IPC)) sess.debugPrintln("Read pipeFile=" + pipeFile.getName() + ", fid=" + fid + ", rdLen=" + rdLen); // Check if there is a valid reply buffered if ( pipeFile.hasBufferedData()) { // Get the buffered data DCEBuffer bufData = pipeFile.getBufferedData(); int bufLen = bufData.getAvailableLength(); // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_IPC)) sess.debugPrintln(" Buffered data available=" + bufLen); // Check if there is less data than the read size if ( rdLen > bufLen) rdLen = bufLen; // Build the read response if ( cmd == PacketType.ReadFile) { // Build the read response smbPkt.setParameterCount(5); smbPkt.setParameter(0, rdLen); for (int i = 1; i < 5; i++) smbPkt.setParameter(i, 0); smbPkt.setByteCount(rdLen + 3); // Copy the data to the response byte[] buf = smbPkt.getBuffer(); int pos = smbPkt.getByteOffset(); buf[pos++] = (byte) DataType.DataBlock; DataPacker.putIntelShort(rdLen, buf, pos); pos += 2; try { bufData.copyData(buf, pos, rdLen); } catch (DCEBufferException ex) { sess.debugPrintln(ex); } } else { // Build the read andX response smbPkt.setParameterCount(12); smbPkt.setAndXCommand(0xFF); for (int i = 1; i < 12; i++) smbPkt.setParameter(i, 0); // Copy the data to the response byte[] buf = smbPkt.getBuffer(); int pos = DCEDataPacker.longwordAlign(smbPkt.getByteOffset()); smbPkt.setParameter(5, rdLen); smbPkt.setParameter(6, pos - RFCNetBIOSProtocol.HEADER_LEN); smbPkt.setByteCount((pos + rdLen) - smbPkt.getByteOffset()); try { bufData.copyData(buf, pos, rdLen); } catch (DCEBufferException ex) { Debug.println(ex); } } } else { // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_IPC)) sess.debugPrintln(" No buffered data available"); // Return a zero length read response if ( cmd == PacketType.ReadFile) { // Initialize the read response smbPkt.setParameterCount(5); for (int i = 0; i < 5; i++) smbPkt.setParameter(i, 0); smbPkt.setByteCount(0); } else { // Return a zero length read andX response smbPkt.setParameterCount(12); smbPkt.setAndXCommand(0xFF); for (int i = 1; i < 12; i++) smbPkt.setParameter(i, 0); smbPkt.setByteCount(0); } } // Clear the status code smbPkt.setLongErrorCode(SMBStatus.NTSuccess); // Send the read reply smbPkt.setFlags2(SMBPacket.FLG2_LONGERRORCODE); sess.sendResponseSMB(smbPkt); } /** * Process the DCE/RPC request buffer * * @param sess SMBSrvSession * @param dceBuf DCEBuffer * @param pipeFile DCEPipeFile * @param smbPkt SMBSrvPacket * @exception IOException * @exception SMBSrvException */ public static final void processDCEBuffer(SMBSrvSession sess, DCEBuffer dceBuf, DCEPipeFile pipeFile, SMBSrvPacket smbPkt) throws IOException, SMBSrvException { // Process the DCE/RPC request switch (dceBuf.getHeaderValue(DCEBuffer.HDR_PDUTYPE)) { // DCE Bind case DCECommand.BIND: procDCEBind(sess, dceBuf, pipeFile, smbPkt); break; // DCE Request case DCECommand.REQUEST: procDCERequest(sess, dceBuf, pipeFile, smbPkt); break; default: sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoAccessRights, SMBStatus.ErrSrv); break; } } /** * Process a DCE bind request * * @param sess SMBSrvSession * @param dceBuf DCEBuffer * @param pipeFile DCEPipeFile * @param smbPkt SMBSrvPacket * @exception IOException * @exception SMBSrvException */ public static final void procDCEBind(SMBSrvSession sess, DCEBuffer dceBuf, DCEPipeFile pipeFile, SMBSrvPacket smbPkt) throws IOException, SMBSrvException { try { // DEBUG if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_DCERPC)) sess.debugPrintln("DCE Bind"); // Get the call id and skip the DCE header int callId = dceBuf.getHeaderValue(DCEBuffer.HDR_CALLID); dceBuf.skipBytes(DCEBuffer.DCEDATA); // Unpack the bind request int maxTxSize = dceBuf.getShort(); int maxRxSize = dceBuf.getShort(); int groupId = dceBuf.getInt(); int ctxElems = dceBuf.getByte(DCEBuffer.ALIGN_INT); int presCtxId = dceBuf.getByte(DCEBuffer.ALIGN_SHORT); int trfSyntax = dceBuf.getByte(DCEBuffer.ALIGN_SHORT); UUID uuid1 = dceBuf.getUUID(true); UUID uuid2 = dceBuf.getUUID(true); // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_DCERPC)) { sess.debugPrintln("Bind: maxTx=" + maxTxSize + ", maxRx=" + maxRxSize + ", groupId=" + groupId + ", ctxElems=" + ctxElems + ", presCtxId=" + presCtxId + ", trfSyntax=" + trfSyntax); sess.debugPrintln(" uuid1=" + uuid1.toString()); sess.debugPrintln(" uuid2=" + uuid2.toString()); } // Update the IPC pipe file pipeFile.setMaxTransmitFragmentSize(maxTxSize); pipeFile.setMaxReceiveFragmentSize(maxRxSize); // Create an output DCE buffer for the reply and add the bind acknowledge header DCEBuffer txBuf = new DCEBuffer(); txBuf.putBindAckHeader(dceBuf.getHeaderValue(DCEBuffer.HDR_CALLID)); txBuf.setHeaderValue(DCEBuffer.HDR_FLAGS, DCEBuffer.FLG_ONLYFRAG); // Pack the bind acknowledge DCE reply txBuf.putShort(maxTxSize); txBuf.putShort(maxRxSize); txBuf.putInt(0x53F0); String srvPipeName = DCEPipeType.getServerPipeName(pipeFile.getPipeId()); txBuf.putShort(srvPipeName.length() + 1); txBuf.putASCIIString(srvPipeName, true, DCEBuffer.ALIGN_INT); txBuf.putInt(1); txBuf.putShort(0); txBuf.putShort(0); txBuf.putUUID(uuid2, true); txBuf.setHeaderValue(DCEBuffer.HDR_FRAGLEN, txBuf.getLength()); // Attach the reply buffer to the pipe file pipeFile.setBufferedData(txBuf); } catch (DCEBufferException ex) { sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv); return; } } /** * Process a DCE request * * @param sess SMBSrvSession * @param inBuf DCEBuffer * @param pipeFile DCEPipeFile * @param smbPkt SMBSrvPacket * @exception IOException * @exception SMBSrvException */ public static final void procDCERequest(SMBSrvSession sess, DCEBuffer inBuf, DCEPipeFile pipeFile, SMBSrvPacket smbPkt) throws IOException, SMBSrvException { // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_DCERPC)) sess.debugPrintln("DCE Request opNum=0x" + Integer.toHexString(inBuf.getHeaderValue(DCEBuffer.HDR_OPCODE))); // Pass the request to the DCE pipe request handler if ( pipeFile.hasRequestHandler()) pipeFile.getRequestHandler().processRequest(sess, inBuf, pipeFile, smbPkt); else sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoAccessRights, SMBStatus.ErrSrv); } }