org.filesys.smb.server.NTProtocolHandler Maven / Gradle / Ivy
Show all versions of jfileserver Show documentation
/*
* Copyright (C) 2006-2013 Alfresco Software Limited.
* Copyright (C) 2018 GK Spencer
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see .
*/
package org.filesys.smb.server;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Time;
import java.util.EnumSet;
import java.util.Set;
import org.filesys.debug.Debug;
import org.filesys.locking.FileLock;
import org.filesys.locking.LockConflictException;
import org.filesys.locking.NotLockedException;
import org.filesys.netbios.RFCNetBIOSProtocol;
import org.filesys.server.auth.ISMBAuthenticator;
import org.filesys.server.auth.InvalidUserException;
import org.filesys.server.auth.acl.AccessControl;
import org.filesys.server.auth.acl.AccessControlManager;
import org.filesys.server.core.InvalidDeviceInterfaceException;
import org.filesys.server.core.ShareType;
import org.filesys.server.core.SharedDevice;
import org.filesys.server.filesys.*;
import org.filesys.server.locking.*;
import org.filesys.smb.*;
import org.filesys.smb.nt.LoadException;
import org.filesys.smb.nt.NTIOCtl;
import org.filesys.smb.nt.SaveException;
import org.filesys.smb.nt.SecurityDescriptor;
import org.filesys.smb.server.notify.NotifyChangeEvent;
import org.filesys.smb.server.notify.NotifyChangeEventList;
import org.filesys.smb.server.notify.NotifyChangeHandler;
import org.filesys.smb.server.notify.NotifyRequest;
import org.filesys.smb.server.ntfs.NTFSStreamsInterface;
import org.filesys.smb.server.ntfs.StreamInfoList;
import org.filesys.util.DataBuffer;
import org.filesys.util.DataPacker;
import org.filesys.util.MemorySize;
import org.filesys.util.WildCard;
/**
* NT SMB Protocol Handler Class
*
*
* The NT protocol handler processes the additional SMBs that were added to the protocol in the NT
* SMB dialect.
*
* @author gkspencer
*/
public class NTProtocolHandler extends CoreProtocolHandler {
// Constants
//
// Flag to enable returning of '.' and '..' directory information in FindFirst request
public static final boolean ReturnDotFiles = true;
// Dummy date/time for dot files
public static final long DotFileDateTime = System.currentTimeMillis();
// Flag to enable faking of oplock requests when opening files
public static final boolean FakeOpLocks = false;
// Number of write requests per file to report file size change notifications
public static final int FileSizeChangeRate = 10;
// Maximum path size that the filesystem accepts
public static final int MaxPathLength = 255;
// NTFS streams information buffer size
public static final int NTFSStreamsInfoBufsize = 4096; // 4K buffer
// Security descriptor to allow Everyone access, returned by the QuerySecurityDescrptor NT
// transaction when NTFS streams are enabled for a virtual filesystem.
private static byte[] _sdEveryOne = {0x01, 0x00, 0x04, (byte) 0x80, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x1c, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x14, 0x00, (byte) 0xff, 0x01, 0x1f, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00};
/**
* Class constructor.
*/
protected NTProtocolHandler() {
super();
}
/**
* Class constructor
*
* @param sess SMBSrvSession
*/
protected NTProtocolHandler(SMBSrvSession sess) {
super(sess);
}
/**
* Return the protocol name
*
* @return String
*/
public String getName() {
return "NT";
}
/**
* Process the NT SMB session setup request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
* @exception TooManyConnectionsException No more connections available
*/
protected void procSessionSetup(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws SMBSrvException, IOException, TooManyConnectionsException {
// Call the authenticator to process the session setup
ISMBAuthenticator smbAuthenticator = m_sess.getSMBServer().getSMBAuthenticator();
try {
// Process the session setup request, build the response
smbAuthenticator.processSessionSetup(m_sess, smbPkt);
}
catch (SMBSrvException ex) {
// Return an error response to the client
m_sess.sendErrorResponseSMB(smbPkt, ex.getNTErrorCode(), ex.getErrorCode(), ex.getErrorClass());
return;
}
// Check if a new packet was allocated for the response
SMBSrvPacket outPkt = smbPkt;
if (smbPkt.hasAssociatedPacket() && parser.hasAndXCommand() == false)
outPkt = outPkt.getAssociatedPacket();
// Check if there is a chained command, or commands
SMBV1Parser respParser = (SMBV1Parser) outPkt.getParser();
int pos = respParser.getLength();
if (parser.hasAndXCommand() && parser.getPosition() < smbPkt.getReceivedLength()) {
// Process any chained commands, AndX
pos = procAndXCommands(outPkt, parser, null);
pos -= RFCNetBIOSProtocol.HEADER_LEN;
// Switch to the response packet
outPkt = smbPkt.getAssociatedPacket();
// Get the associated parser
respParser = (SMBV1Parser) outPkt.getParser();
}
else {
// Indicate that there are no chained replies
respParser.setAndXCommand(SMBV1.NO_ANDX_CMD);
}
// Make sure the packet is a response
respParser.setResponse();
// Send the session setup response
m_sess.sendResponseSMB(outPkt, pos);
// Update the session state if the response indicates a success status. A multi stage
// session setup response returns a warning status.
if (respParser.getLongErrorCode() == SMBStatus.NTSuccess) {
// Update the session state
m_sess.setState(SessionState.SMB_SESSION);
// Find the virtual circuit allocated, this will set the per-thread ClientInfo on the session
m_sess.findVirtualCircuit(respParser.getUserId());
// Notify listeners that a user has logged onto the session
m_sess.getSMBServer().sessionLoggedOn(m_sess);
}
}
/**
* Process the chained SMB commands (AndX).
*
* @param smbPkt Request packet.
* @param parser SMBV1Parser
* @param file Current file , or null if no file context in chain
* @return New offset to the end of the reply packet
*/
protected final int procAndXCommands(SMBSrvPacket smbPkt, SMBV1Parser parser, NetworkFile file) {
// Get the response packet
SMBSrvPacket respPkt = smbPkt.getAssociatedPacket();
if (respPkt == null)
throw new RuntimeException("No response packet allocated for AndX request");
// Get the chained command and command block offset
int andxCmd = parser.getAndXCommand();
int andxOff = parser.getParameter(1) + RFCNetBIOSProtocol.HEADER_LEN;
// Set the parser for the response
respPkt.setParser( SMBSrvPacket.Version.V1);
SMBV1Parser respParser = (SMBV1Parser) respPkt.getParser();
// Set the initial chained command and offset
respParser.setAndXCommand(andxCmd);
int endOfPkt = respParser.getByteOffset() + respParser.getByteCount();
respParser.setParameter(1, endOfPkt - RFCNetBIOSProtocol.HEADER_LEN);
// Pointer to the last parameter block, starts with the main command parameter block
int paramBlk = SMBV1.WORDCNT;
// Get the current end of the reply packet offset
boolean andxErr = false;
while (andxCmd != SMBV1.NO_ANDX_CMD && andxErr == false) {
// Determine the chained command type
int prevEndOfPkt = endOfPkt;
boolean endOfChain = false;
switch (andxCmd) {
// Tree connect
case PacketTypeV1.TreeConnectAndX:
endOfPkt = procChainedTreeConnectAndX(andxOff, smbPkt, respPkt, endOfPkt);
break;
// Close file
case PacketTypeV1.CloseFile:
endOfPkt = procChainedClose(andxOff, smbPkt, respPkt, endOfPkt);
endOfChain = true;
break;
// Read file
case PacketTypeV1.ReadAndX:
endOfPkt = procChainedReadAndX(andxOff, smbPkt, respPkt, endOfPkt, file);
break;
// Chained command was not handled
default:
if (Debug.EnableError)
Debug.println("<<<<< Chained command : 0x" + Integer.toHexString(andxCmd) + " Not Processed >>>>>");
break;
}
// Set the next chained command details in the current parameter block
respParser.setAndXCommand(paramBlk, andxCmd);
respParser.setAndXParameter(paramBlk, 1, prevEndOfPkt - RFCNetBIOSProtocol.HEADER_LEN);
// Check if the end of chain has been reached, if not then look for the next chained command in the request.
// End of chain might be set if the current command is not an AndX SMB command.
if (endOfChain == false) {
// Advance to the next chained command block
andxCmd = parser.getAndXParameter(andxOff, 0) & 0x00FF;
andxOff = parser.getAndXParameter(andxOff, 1);
// Advance the current parameter block
paramBlk = prevEndOfPkt;
}
else {
// Indicate that the end of the command chain has been reached
andxCmd = SMBV1.NO_ANDX_CMD;
}
// Check if the chained command has generated an error status
if (respParser.getErrorCode() != SMBStatus.Success)
andxErr = true;
}
// Return the offset to the end of the reply packet
return endOfPkt;
}
/**
* Process a chained tree connect request.
*
* @param cmdOff int Offset to the chained command within the request packet.
* @param smbPkt Request packet.
* @param respPkt Response packet
* @param endOff int Offset to the current end of the reply packet.
* @return New end of reply offset.
*/
protected final int procChainedTreeConnectAndX(int cmdOff, SMBSrvPacket smbPkt, SMBSrvPacket respPkt, int endOff) {
// Get the request and response parsers
SMBV1Parser reqParser = (SMBV1Parser) smbPkt.getParser();
SMBV1Parser respParser = (SMBV1Parser) respPkt.getParser();
// Extract the parameters
int flags = reqParser.getAndXParameter(cmdOff, 2);
int pwdLen = reqParser.getAndXParameter(cmdOff, 3);
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(respParser.getUserId());
if (vc == null) {
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return endOff;
}
// Reset the byte pointer for data unpacking
reqParser.setBytePointer(reqParser.getAndXByteOffset(cmdOff), reqParser.getAndXByteCount(cmdOff));
// Extract the password string
String pwd = null;
if (pwdLen > 0) {
byte[] pwdByt = reqParser.unpackBytes(pwdLen);
pwd = new String(pwdByt);
}
// Extract the requested share name, as a UNC path
boolean unicode = reqParser.isUnicode();
String uncPath = reqParser.unpackString(unicode);
if (uncPath == null) {
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return endOff;
}
// Extract the service type string
String service = reqParser.unpackString(false);
if (service == null) {
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return endOff;
}
// Convert the service type to a shared device type, client may specify '?????' in which case we ignore the error.
ShareType servType = ShareType.ServiceAsType(service);
if (servType == ShareType.UNKNOWN && service.compareTo("?????") != 0) {
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return endOff;
}
// Debug
if (m_sess.hasDebug(SMBSrvSession.Dbg.TREE))
m_sess.debugPrintln("NT ANDX Tree Connect AndX - " + uncPath + ", " + service);
// Parse the requested share name
PCShare share = null;
try {
share = new PCShare(uncPath);
}
catch (InvalidUNCPathException ex) {
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return endOff;
}
// Map the IPC$ share to the admin pipe type
if (share.getShareName().compareTo("IPC$") == 0)
servType = ShareType.ADMINPIPE;
// Check if the session is a null session, only allow access to the IPC$ named pipe share
if (m_sess.hasClientInformation() && m_sess.getClientInformation().isNullSession() && servType != ShareType.ADMINPIPE) {
// Return an error status
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return endOff;
}
// Find the requested shared device
SharedDevice shareDev = null;
try {
// Get/create the shared device
shareDev = m_sess.getSMBServer().findShare(share.getNodeName(), share.getShareName(), servType, m_sess, true);
}
catch (InvalidUserException ex) {
// Return a logon failure status
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return endOff;
}
catch (Exception ex) {
// Return a general status, bad network name
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTBadNetName, SMBStatus.SRVInvalidNetworkName, SMBStatus.ErrSrv);
return endOff;
}
// Check if the share is valid
if (shareDev == null || (servType != ShareType.UNKNOWN && shareDev.getType() != servType)) {
// Set the error status
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTBadNetName, SMBStatus.SRVInvalidNetworkName, SMBStatus.ErrSrv);
return endOff;
}
// Authenticate the share connect, if the server is using share mode security
ISMBAuthenticator auth = getSession().getSMBServer().getSMBAuthenticator();
ISMBAuthenticator.ShareStatus sharePerm = ISMBAuthenticator.ShareStatus.WRITEABLE;
if (auth != null && auth.getAccessMode() == ISMBAuthenticator.AuthMode.SHARE) {
// Validate the share connection
sharePerm = auth.authenticateShareConnect(m_sess.getClientInformation(), shareDev, pwd, m_sess);
if (sharePerm == ISMBAuthenticator.ShareStatus.NO_ACCESS) {
// Invalid share connection request
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return endOff;
}
}
// Check if there is an access control manager, if so then run any access controls to
// determine the sessions access to the share.
if (getSession().getServer().hasAccessControlManager() && shareDev.hasAccessControls()) {
// Get the access control manager
AccessControlManager aclMgr = getSession().getServer().getAccessControlManager();
// Update the access permission for this session by processing the access control list
// for the shared device
int aclPerm = aclMgr.checkAccessControl(getSession(), shareDev);
if (aclPerm == FileAccess.NoAccess) {
// Invalid share connection request
respParser.setError(reqParser.isLongErrorCode(), SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return endOff;
}
// If the access controls returned a new access type update the main permission
if (aclPerm != AccessControl.Default)
sharePerm = asShareStatus( aclPerm);
}
// Allocate a tree id for the new connection
TreeConnection tree = null;
try {
// Allocate the tree id for this connection
int treeId = vc.addConnection(shareDev);
respParser.setTreeId(treeId);
// Set the file permission that this user has been granted for this share
tree = vc.findConnection(treeId);
tree.setPermission(sharePerm);
// Inform the driver that a connection has been opened
if (tree.getInterface() != null)
tree.getInterface().treeOpened(m_sess, tree);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TREE))
m_sess.debugPrintln("ANDX Tree Connect AndX - Allocated Tree Id = " + treeId);
}
catch (TooManyConnectionsException ex) {
// Too many connections open at the moment
respParser.setError(SMBStatus.SRVNoResourcesAvailable, SMBStatus.ErrSrv);
return endOff;
}
// Build the tree connect response
respParser.setAndXParameterCount(endOff, 2);
respParser.setAndXParameter(endOff, 0, SMBV1.NO_ANDX_CMD);
respParser.setAndXParameter(endOff, 1, 0);
// Pack the service type
int pos = respParser.getAndXByteOffset(endOff);
byte[] outBuf = respParser.getBuffer();
pos = DataPacker.putString(ShareType.TypeAsService(shareDev.getType()), outBuf, pos, true);
// Determine the filesystem type, for disk shares
String devType = "";
try {
// Check if this is a disk shared device
if (shareDev.getType() == ShareType.DISK) {
// Check if the filesystem driver implements the NTFS streams interface, and streams
// are enabled
if (shareDev.getInterface() instanceof NTFSStreamsInterface) {
// Check if NTFS streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) shareDev.getInterface();
if (ntfsStreams.hasStreamsEnabled(m_sess, tree))
devType = FileSystem.TypeNTFS;
}
else {
// Get the filesystem type from the context
DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext();
devType = diskCtx.getFilesystemType();
}
}
}
catch (InvalidDeviceInterfaceException ex) {
// Debug
if (Debug.EnableError && m_sess.hasDebug(SMBSrvSession.Dbg.TREE))
Debug.println("ANDX TreeConnectAndX error " + ex.getMessage());
}
// Pack the filesystem type
pos = DataPacker.putString(devType, outBuf, pos, true, respParser.isUnicode());
int bytLen = pos - respParser.getAndXByteOffset(endOff);
respParser.setAndXByteCount(endOff, bytLen);
// Return the new end of packet offset
return pos;
}
/**
* Process a chained read file request
*
* @param cmdOff Offset to the chained command within the request packet.
* @param smbPkt Request packet.
* @param respPkt Response packet
* @param endOff Offset to the current end of the reply packet.
* @param netFile File to be read, passed down the chained requests
* @return New end of reply offset.
*/
protected final int procChainedReadAndX(int cmdOff, SMBSrvPacket smbPkt, SMBSrvPacket respPkt, int endOff, NetworkFile netFile) {
// Get the request and response parsers
SMBV1Parser reqParser = (SMBV1Parser) smbPkt.getParser();
SMBV1Parser respParser = (SMBV1Parser) respPkt.getParser();
// Get the tree id from the received packet and validate that it is a valid connection id.
TreeConnection conn = m_sess.findTreeConnection(smbPkt);
if (conn == null) {
respParser.setError(SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return endOff;
}
// Extract the read file parameters
long offset = (long) reqParser.getAndXParameterLong(cmdOff, 3); // bottom 32bits of read offset
offset &= 0xFFFFFFFFL;
int maxCount = reqParser.getAndXParameter(cmdOff, 5);
// Check for the NT format request that has the top 32bits of the file offset
if (reqParser.getAndXParameterCount(cmdOff) == 12) {
long topOff = (long) reqParser.getAndXParameterLong(cmdOff, 10);
offset += topOff << 32;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("Chained File Read AndX : Size=" + maxCount + " ,Pos=" + offset);
// Read data from the file
byte[] buf = respPkt.getBuffer();
int dataPos = 0;
int rdlen = 0;
try {
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Set the returned parameter count so that the byte offset can be calculated
respParser.setAndXParameterCount(endOff, 12);
dataPos = respParser.getAndXByteOffset(endOff);
dataPos = DataPacker.wordAlign(dataPos); // align the data buffer
// Check if the requested data length will fit into the buffer
int dataLen = buf.length - dataPos;
if (dataLen < maxCount)
maxCount = dataLen;
// Read from the file
// Synchronize reads using the network file
synchronized (netFile) {
rdlen = disk.readFile(m_sess, conn, netFile, buf, dataPos, maxCount, offset);
}
// Return the data block
respParser.setAndXParameter(endOff, 0, SMBV1.NO_ANDX_CMD);
respParser.setAndXParameter(endOff, 1, 0);
respParser.setAndXParameter(endOff, 2, 0); // bytes remaining, for pipes only
respParser.setAndXParameter(endOff, 3, 0); // data compaction mode
respParser.setAndXParameter(endOff, 4, 0); // reserved
respParser.setAndXParameter(endOff, 5, rdlen); // data length
respParser.setAndXParameter(endOff, 6, dataPos - RFCNetBIOSProtocol.HEADER_LEN); // offset to data
// Clear the reserved parameters
for (int i = 7; i < 12; i++)
respParser.setAndXParameter(endOff, i, 0);
// Set the byte count
respParser.setAndXByteCount(endOff, (dataPos + rdlen) - respParser.getAndXByteOffset(endOff));
// Update the end offset for the new end of packet
endOff = dataPos + rdlen;
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
respParser.setError(SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return endOff;
}
catch (java.io.IOException ex) {
}
// Return the new end of packet offset
return endOff;
}
/**
* Process a chained close file request
*
* @param cmdOff int Offset to the chained command within the request packet.
* @param smbPkt Request packet.
* @param respPkt Response packet
* @param endOff int Offset to the current end of the reply packet.
* @return New end of reply offset.
*/
protected final int procChainedClose(int cmdOff, SMBSrvPacket smbPkt, SMBSrvPacket respPkt, int endOff) {
// Get the request and response parsers
SMBV1Parser reqParser = (SMBV1Parser) smbPkt.getParser();
SMBV1Parser respParser = (SMBV1Parser) respPkt.getParser();
// Get the tree id from the received packet and validate that it is a valid connection id.
TreeConnection conn = m_sess.findTreeConnection(smbPkt);
if (conn == null) {
respParser.setError(SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return endOff;
}
// Get the file id from the request
int fid = reqParser.getAndXParameter(cmdOff, 0);
int ftime = reqParser.getAndXParameter(cmdOff, 1);
int fdate = reqParser.getAndXParameter(cmdOff, 2);
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
respParser.setError(SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return endOff;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("Chained File Close [" + reqParser.getTreeId() + "] fid=" + fid);
// Close the file
try {
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Close the file
//
// The disk interface may be null if the file is a named pipe file
if (disk != null)
disk.closeFile(m_sess, conn, netFile);
// Indicate that the file has been closed
netFile.setClosed(true);
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
respParser.setError(SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return endOff;
}
catch (java.io.IOException ex) {
}
// Clear the returned parameter count and byte count
respParser.setAndXParameterCount(endOff, 0);
respParser.setAndXByteCount(endOff, 0);
endOff = respParser.getAndXByteOffset(endOff) - RFCNetBIOSProtocol.HEADER_LEN;
// Remove the file from the connections list of open files
conn.removeFile(fid, getSession());
// Return the new end of packet offset
return endOff;
}
/**
* Process the SMB tree connect request.
*
* @param smbPkt Request packet.
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
* @exception TooManyConnectionsException No more connections available
*/
protected void procTreeConnectAndX(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws SMBSrvException, TooManyConnectionsException, java.io.IOException {
// Check that the received packet looks like a valid tree connect request
if (parser.checkPacketIsValid(4, 3) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Extract the parameters
int flags = parser.getParameter(2);
int pwdLen = parser.getParameter(3);
// Initialize the byte area pointer
parser.resetBytePointer();
// Determine if ASCII or unicode strings are being used
boolean unicode = parser.isUnicode();
// Extract the password string
String pwd = null;
if (pwdLen > 0) {
byte[] pwdByts = parser.unpackBytes(pwdLen);
pwd = new String(pwdByts);
}
// Extract the requested share name, as a UNC path
String uncPath = parser.unpackString(unicode);
if (uncPath == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Extract the service type string, always seems to be ASCII
String service = parser.unpackString(false);
if (service == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Convert the service type to a shared device type, client may specify '?????' in which case we ignore the error.
ShareType servType = ShareType.ServiceAsType(service);
if (servType == ShareType.UNKNOWN && service.compareTo("?????") != 0) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TREE))
m_sess.debugPrintln("NT Tree Connect AndX - " + uncPath + ", " + service + ", flags=" + TreeConnectAndX.asStringRequest(flags) + "/0x" + Integer.toHexString(flags));
// Parse the requested share name
String shareName = null;
String hostName = null;
if (uncPath.startsWith("\\")) {
try {
PCShare share = new PCShare(uncPath);
shareName = share.getShareName();
hostName = share.getNodeName();
}
catch (InvalidUNCPathException ex) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
}
else
shareName = uncPath;
// Map the IPC$ share to the admin pipe type
if (shareName.compareTo("IPC$") == 0)
servType = ShareType.ADMINPIPE;
// Check if the session is a null session, only allow access to the IPC$ named pipe share
if (m_sess.hasClientInformation() && m_sess.getClientInformation().isNullSession() && servType != ShareType.ADMINPIPE) {
// Return an error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Find the requested shared device
SharedDevice shareDev = null;
try {
// Get/create the shared device
shareDev = m_sess.getSMBServer().findShare(hostName, shareName, servType, m_sess, true);
}
catch (InvalidUserException ex) {
// Return a logon failure status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (Exception ex) {
// Return a general status, bad network name
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTBadNetName, SMBStatus.SRVInvalidNetworkName, SMBStatus.ErrSrv);
return;
}
// Check if the share is valid
if (shareDev == null || (servType != ShareType.UNKNOWN && shareDev.getType() != servType)) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTBadNetName, SMBStatus.SRVInvalidNetworkName, SMBStatus.ErrSrv);
return;
}
// Authenticate the share connection depending upon the security mode the server is running under
ISMBAuthenticator auth = getSession().getSMBServer().getSMBAuthenticator();
ISMBAuthenticator.ShareStatus sharePerm = ISMBAuthenticator.ShareStatus.WRITEABLE;
if (auth != null) {
// Validate the share connection
sharePerm = auth.authenticateShareConnect(m_sess.getClientInformation(), shareDev, pwd, m_sess);
if (sharePerm == ISMBAuthenticator.ShareStatus.NO_ACCESS) {
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TREE))
m_sess.debugPrint("Tree connect to " + shareName + ", access denied");
// Invalid share connection request
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
}
// Check if there is an access control manager, if so then run any access controls to determine the sessions
// access to the share.
if (getSession().getServer().hasAccessControlManager() && shareDev.hasAccessControls()) {
// Get the access control manager
AccessControlManager aclMgr = getSession().getServer().getAccessControlManager();
// Update the access permission for this session by processing the access control list
// for the shared device
int aclPerm = aclMgr.checkAccessControl(getSession(), shareDev);
if (aclPerm == FileAccess.NoAccess) {
// Invalid share connection request
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// If the access controls returned a new access type update the main permission
if (aclPerm != AccessControl.Default)
sharePerm = asShareStatus( aclPerm);
}
// Allocate a tree id for the new connection
int treeId = vc.addConnection(shareDev);
parser.setTreeId(treeId);
// Set the file permission that this user has been granted for this share
TreeConnection tree = vc.findConnection(treeId);
tree.setPermission(sharePerm);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TREE))
m_sess.debugPrintln("Tree Connect AndX - Allocated Tree Id = " + treeId + ", Permission = "
+ sharePerm.name() + ", extendedResponse=" + TreeConnectAndX.hasExtendedResponse(flags));
// Check if an extended format response is required, only return for filesystem shares
if (TreeConnectAndX.hasExtendedResponse(flags) && servType != ShareType.ADMINPIPE) {
// Build the extended tree connect response
parser.setParameterCount(7);
parser.setAndXCommand(0xFF); // no chained reply
parser.setParameter(1, 0);
parser.setParameter(2, 0); // response flags
// Maximal user access rights
if (sharePerm == ISMBAuthenticator.ShareStatus.WRITEABLE)
parser.setParameterLong(3, AccessMode.NTFileGenericAll);
else
parser.setParameterLong(3, AccessMode.NTFileGenericRead);
// Guest maximal access rights
parser.setParameterLong(5, 0);
}
else {
// Build the standard tree connect response
parser.setParameterCount(3);
parser.setAndXCommand(0xFF); // no chained reply
parser.setParameter(1, 0);
parser.setParameter(2, 0); // response flags
}
// Pack the service type
int pos = parser.getByteOffset();
pos = DataPacker.putString(ShareType.TypeAsService(shareDev.getType()), smbPkt.getBuffer(), pos, true);
// Determine the filesystem type, for disk shares
String devType = "";
try {
// Check if this is a disk shared device
if (shareDev.getType() == ShareType.DISK) {
// Check if the filesystem driver implements the NTFS streams interface, and streams are enabled
if (shareDev.getInterface() instanceof NTFSStreamsInterface) {
// Check if NTFS streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) shareDev.getInterface();
if (ntfsStreams.hasStreamsEnabled(m_sess, tree))
devType = "NTFS";
}
else {
// Get the filesystem type from the context
DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext();
devType = diskCtx.getFilesystemType();
}
}
}
catch (InvalidDeviceInterfaceException ex) {
// Log the error
if (Debug.EnableError && m_sess.hasDebug(SMBSrvSession.Dbg.TREE))
Debug.println("TreeConnectAndX error " + ex.getMessage());
}
// Pack the filesystem type
pos = DataPacker.wordAlign(pos);
pos = DataPacker.putString(devType, smbPkt.getBuffer(), pos, true, parser.isUnicode());
parser.setByteCount(pos - parser.getByteOffset());
// Send the response
m_sess.sendResponseSMB(smbPkt);
// Inform the driver that a connection has been opened
if (tree.getInterface() != null)
tree.getInterface().treeOpened(m_sess, tree);
}
/**
* Close a file that has been opened on the server.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected void procCloseFile(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid file close request
if (parser.checkPacketIsValid(3, 0) == false) {
m_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
TreeConnection conn = m_sess.findTreeConnection(smbPkt);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoAccessRights, SMBStatus.ErrSrv);
return;
}
// Get the file id from the request
int fid = parser.getParameter(0);
int ftime = parser.getParameter(1);
int fdate = parser.getParameter(2);
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("File close [" + parser.getTreeId() + "] fid=" + fid + ", fileId=" + netFile.getFileId());
// Close the file
boolean delayedClose = false;
try {
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Close the file
//
// The disk interface may be null if the file is a named pipe file
if (disk != null) {
// DEBUG
long startTime = 0L;
if (netFile.hasDeleteOnClose() && Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.BENCHMARK))
startTime = System.currentTimeMillis();
// Check if the file has an oplock
if (netFile.hasOpLock())
OpLockHelper.releaseOpLock(m_sess, smbPkt, disk, conn, netFile);
// Close the file
disk.closeFile(m_sess, conn, netFile);
// Release any byte range locks that are on the file
if (netFile.hasLocks() && disk instanceof FileLockingInterface) {
// Get the lock manager
FileLockingInterface flIface = (FileLockingInterface) disk;
LockManager lockMgr = flIface.getLockManager(m_sess, conn);
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.LOCK))
Debug.println("Releasing locks for closed file, file=" + netFile.getFullName() + ", locks=" + netFile.numberOfLocks());
// Release all locks on the file owned by this session
lockMgr.releaseLocksForFile(m_sess, conn, netFile);
}
// Check if the file close has been delayed by the filesystem driver
if (netFile.hasDelayedClose()) {
delayedClose = true;
// Reset the delayed close status
netFile.setDelayedClose(false);
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("File close delayed [" + parser.getTreeId() + "] fid=" + fid + ", path=" + netFile.getFullName());
}
// DEBUG
if (startTime != 0L && Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.BENCHMARK))
Debug.println("Benchmark: Delete on close " + netFile.getName() + " took " + (System.currentTimeMillis() - startTime) + "ms");
}
// Indicate that the file has been closed
if (delayedClose == false)
netFile.setClosed(true);
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.BENCHMARK)) {
if (netFile.isDirectory() == false) {
if (netFile.wasCreated() && netFile.getWriteCount() > 0)
m_sess.debugPrintln("Benchmark: File=" + netFile.getFullName() + ", Size=" + MemorySize.asScaledString(netFile.getFileSize()) +
", Write Time=" + (System.currentTimeMillis() - netFile.getCreationDate()) + "ms" +
", ClosedAt=" + new Time(System.currentTimeMillis()));
}
else if (netFile.getCreationDate() != 0L)
m_sess.debugPrintln("Benchmark: Dir=" + netFile.getFullName() +
", Write Time=" + (System.currentTimeMillis() - netFile.getCreationDate()) + "ms, CreatedAt=" + new Time(netFile.getCreationDate()));
else
m_sess.debugPrintln("Benchmark: Dir=" + netFile.getFullName() + ", ClosedAt=" + new Time(System.currentTimeMillis()));
}
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Not allowed to delete the file, when the delete on close flag has been set
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (Throwable t) {
}
// Remove the file from the connections list of open files
if (delayedClose == false)
conn.removeFile(fid, getSession());
// Build the close file response
parser.setParameterCount(0);
parser.setByteCount(0);
// Send the response packet
m_sess.sendResponseSMB(smbPkt);
// Check if there are any file/directory change notify requests active
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if ( diskCtx != null && diskCtx.hasFileServerNotifications()) {
if (netFile.getWriteCount() > 0)
diskCtx.getChangeHandler().notifyFileSizeChanged(netFile.getFullName());
if (netFile.hasDeleteOnClose())
diskCtx.getChangeHandler().notifyFileChanged(NotifyAction.Removed, netFile.getFullName());
}
}
/**
* Process a transact2 request. The transact2 can contain many different sub-requests.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected void procTransact2(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that we received enough parameters for a transact2 request
if (parser.checkPacketIsValid(14, 0) == false) {
// Not enough parameters for a valid transact2 request
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree id from the received packet and validate that it is a valid connection id
TreeConnection conn = vc.findConnection(parser.getTreeId());
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoAccessRights, SMBStatus.ErrSrv);
return;
}
// Create a transact packet using the received SMB packet
SMBSrvTransPacket tranPkt = new SMBSrvTransPacket(parser);
// Create a transact buffer to hold the transaction setup, parameter and data blocks
SrvTransactBuffer transBuf = null;
int subCmd = tranPkt.getSubFunction();
if (tranPkt.getTotalParameterCount() == tranPkt.getRxParameterBlockLength()
&& tranPkt.getTotalDataCount() == tranPkt.getRxDataBlockLength()) {
// Create a transact buffer using the packet buffer, the entire request is contained in
// a single packet
transBuf = new SrvTransactBuffer(tranPkt);
}
else {
// Create a transact buffer to hold the multiple transact request parameter/data blocks
transBuf = new SrvTransactBuffer(tranPkt.getSetupCount(), tranPkt.getTotalParameterCount(), tranPkt.getTotalDataCount());
transBuf.setType(parser.getCommand());
transBuf.setFunction(subCmd);
// Append the setup, parameter and data blocks to the transaction data
byte[] buf = tranPkt.getBuffer();
transBuf.appendSetup(buf, tranPkt.getSetupOffset(), tranPkt.getSetupCount() * 2);
transBuf.appendParameter(buf, tranPkt.getRxParameterBlock(), tranPkt.getRxParameterBlockLength());
transBuf.appendData(buf, tranPkt.getRxDataBlock(), tranPkt.getRxDataBlockLength());
}
// Set the return data limits for the transaction
transBuf.setReturnLimits(tranPkt.getMaximumReturnSetupCount(), tranPkt.getMaximumReturnParameterCount(), tranPkt.getMaximumReturnDataCount());
// Clear the transaction packet buffer, as it is owned by the original packet
tranPkt.setBuffer(null);
// Check for a multi-packet transaction, for a multi-packet transaction we just acknowledge
// the receive with an empty response SMB
if (transBuf.isMultiPacket()) {
// Save the partial transaction data
vc.setTransaction(transBuf);
// Send an intermediate acknowedgement response
m_sess.sendSuccessResponseSMB(smbPkt);
return;
}
// Check if the transaction is on the IPC$ named pipe, the request requires special processing
if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
IPCHandler.procTransaction(vc, transBuf, m_sess, smbPkt);
return;
}
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("Transaction [" + parser.getTreeId() + "] tbuf=" + transBuf);
// Process the transaction buffer
processTransactionBuffer(transBuf, smbPkt, parser);
}
/**
* Process a transact2 secondary request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected void procTransact2Secondary(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that we received enough parameters for a transact2 request
if (parser.checkPacketIsValid(8, 0) == false) {
// Not enough parameters for a valid transact2 request
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree id from the received packet and validate that it is a valid
// connection id.
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoAccessRights, SMBStatus.ErrSrv);
return;
}
// Check if there is an active transaction, and it is an NT transaction
if (vc.hasTransaction() == false
|| (vc.getTransaction().isType() == PacketTypeV1.Transaction && parser.getCommand() != PacketTypeV1.TransactionSecond)
|| (vc.getTransaction().isType() == PacketTypeV1.Transaction2 && parser.getCommand() != PacketTypeV1.Transaction2Second)) {
// No transaction to continue, or packet does not match the existing transaction, return
// an error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Create an NT transaction using the received packet
SMBSrvTransPacket tpkt = new SMBSrvTransPacket(parser.getBuffer());
byte[] buf = tpkt.getBuffer();
SrvTransactBuffer transBuf = vc.getTransaction();
// Append the parameter data to the transaction buffer, if any
int plen = tpkt.getSecondaryParameterBlockCount();
if (plen > 0) {
// Append the data to the parameter buffer
DataBuffer paramBuf = transBuf.getParameterBuffer();
paramBuf.appendData(buf, tpkt.getSecondaryParameterBlockOffset(), plen);
}
// Append the data block to the transaction buffer, if any
int dlen = tpkt.getSecondaryDataBlockCount();
if (dlen > 0) {
// Append the data to the data buffer
DataBuffer dataBuf = transBuf.getDataBuffer();
dataBuf.appendData(buf, tpkt.getSecondaryDataBlockOffset(), dlen);
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("Transaction Secondary [" + treeId + "] paramLen=" + plen + ", dataLen=" + dlen);
// Check if the transaction has been received or there are more sections to be received
int totParam = tpkt.getTotalParameterCount();
int totData = tpkt.getTotalDataCount();
int paramDisp = tpkt.getParameterBlockDisplacement();
int dataDisp = tpkt.getDataBlockDisplacement();
if ((paramDisp + plen) == totParam && (dataDisp + dlen) == totData) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("Transaction complete, processing ...");
// Clear the in progress transaction
vc.setTransaction(null);
// Check if the transaction is on the IPC$ named pipe, the request requires special processing
if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
IPCHandler.procTransaction(vc, transBuf, m_sess, smbPkt);
return;
}
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("Transaction second [" + treeId + "] tbuf=" + transBuf);
// Process the transaction
processTransactionBuffer(transBuf, smbPkt, parser);
}
else {
// There are more transaction parameter/data sections to be received, return an
// intermediate response
m_sess.sendSuccessResponseSMB(smbPkt);
}
}
/**
* Process a transaction buffer
*
* @param tbuf TransactBuffer
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @throws IOException If a network error occurs
* @throws SMBSrvException If an SMB error occurs
*/
private final void processTransactionBuffer(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the transact2 sub-command code and process the request
switch (tbuf.getFunction()) {
// Start a file search
case PacketTypeV1.Trans2FindFirst:
procTrans2FindFirst(tbuf, smbPkt, parser);
break;
// Continue a file search
case PacketTypeV1.Trans2FindNext:
procTrans2FindNext(tbuf, smbPkt, parser);
break;
// Query file system information
case PacketTypeV1.Trans2QueryFileSys:
procTrans2QueryFileSys(tbuf, smbPkt, parser);
break;
// Query path
case PacketTypeV1.Trans2QueryPath:
procTrans2QueryPath(tbuf, smbPkt, parser);
break;
// Query file information via handle
case PacketTypeV1.Trans2QueryFile:
procTrans2QueryFile(tbuf, smbPkt, parser);
break;
// Set file information via handle
case PacketTypeV1.Trans2SetFile:
procTrans2SetFile(tbuf, smbPkt, parser);
break;
// Set file information via path
case PacketTypeV1.Trans2SetPath:
procTrans2SetPath(tbuf, smbPkt, parser);
break;
// Unknown transact2 command
default:
// Return an unrecognized command error
if (Debug.EnableError)
m_sess.debugPrintln("NT Error Transact2 Command = 0x" + Integer.toHexString(tbuf.getFunction()));
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
break;
}
}
/**
* Close a search started via the transact2 find first/next command.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procFindClose(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid find close request
if (parser.checkPacketIsValid(1, 0) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoAccessRights, SMBStatus.ErrSrv);
return;
}
// Get the search id
int searchId = parser.getParameter(0);
// Get the search context
SearchContext ctx = vc.getSearchContext(searchId);
if (ctx == null) {
// Invalid search handle
m_sess.sendSuccessResponseSMB(smbPkt);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Close trans search [" + searchId + "]");
// Deallocate the search slot, close the search.
vc.deallocateSearchSlot(searchId);
// Return a success status SMB
m_sess.sendSuccessResponseSMB(smbPkt);
}
/**
* Process the file lock/unlock request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procLockingAndX(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid locking andX request
if (parser.checkPacketIsValid(8, 0) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoAccessRights, SMBStatus.ErrSrv);
return;
}
// Extract the file lock/unlock parameters
int fid = parser.getParameter(2);
int lockType = parser.getParameter(3);
long lockTmo = parser.getParameterLong(4);
int unlockCnt = parser.getParameter(6);
int lockCnt = parser.getParameter(7);
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.Win32InvalidHandle, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.LOCK))
m_sess.debugPrintln("File Lock [" + netFile.getFileId() + "] : type=0x" + Integer.toHexString(lockType) + ", tmo="
+ lockTmo + ", locks=" + lockCnt + ", unlocks=" + unlockCnt);
DiskInterface disk = null;
try {
// Get the disk interface for the share
disk = (DiskInterface) conn.getSharedDevice().getInterface();
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Check for an oplock break
if (LockingAndX.hasOplockBreak(lockType)) {
// Debug
if (Debug.EnableDbg && m_sess.hasDebug(SMBSrvSession.Dbg.OPLOCK))
Debug.println("Oplock break, flags=0x" + Integer.toHexString(lockType) + " file=" + netFile);
// Access the oplock manager via the filesystem
if (disk instanceof OpLockInterface) {
// Get the oplock manager
OpLockInterface oplockIface = (OpLockInterface) disk;
OpLockManager oplockMgr = oplockIface.getOpLockManager(m_sess, conn);
if (oplockMgr == null) {
// DEBUG
if (Debug.EnableDbg && m_sess.hasDebug(SMBSrvSession.Dbg.OPLOCK))
Debug.print(" OpLock manager is null, tree=" + conn);
// Return a not supported error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
return;
}
// Get the oplock details for the file
OpLockDetails oplock = oplockMgr.getOpLockDetails(netFile.getFullName());
if (oplock == null) {
// Return a not locked error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTRangeNotLocked, SMBStatus.DOSNotLocked, SMBStatus.ErrDos);
return;
}
// Check if the oplock should be released or converted to a shared Level II oplock
if (LockingAndX.hasLevelIIOplock(lockType) == false) {
// Release the oplock
oplockMgr.releaseOpLock(oplock.getPath());
// DEBUG
if (Debug.EnableDbg && m_sess.hasDebug(SMBSrvSession.Dbg.OPLOCK))
Debug.println(" Oplock released, oplock=" + oplock);
}
else {
// Change the oplock type to a LevelII
oplockMgr.changeOpLockType(oplock, OpLockType.LEVEL_II);
// DEBUG
if (Debug.EnableDbg && m_sess.hasDebug(SMBSrvSession.Dbg.OPLOCK))
Debug.println(" Oplock converted to LevelII, oplock=" + oplock);
}
}
else {
// Return a not supported error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
return;
}
}
// Check for byte range locks/unlocks
if (unlockCnt > 0 || lockCnt > 0) {
// Check if the virtual filesystem supports file locking
if (disk instanceof FileLockingInterface) {
// Get the lock manager
FileLockingInterface lockInterface = (FileLockingInterface) disk;
LockManager lockMgr = lockInterface.getLockManager(m_sess, conn);
// Unpack the lock/unlock structures
parser.resetBytePointer();
boolean largeFileLock = LockingAndX.hasLargeFiles(lockType);
int lockIdx = 0;
while (lockIdx < (unlockCnt + lockCnt)) {
// Get the unlock/lock structure
int pid = parser.unpackWord();
long offset = -1;
long length = -1;
if (largeFileLock == false) {
// Get the lock offset and length, short format
offset = parser.unpackInt();
length = parser.unpackInt();
}
else {
// Get the lock offset and length, large format
parser.skipBytes(2);
offset = ((long) parser.unpackInt()) << 32;
offset += (long) parser.unpackInt();
length = ((long) parser.unpackInt()) << 32;
length += (long) parser.unpackInt();
}
// Create the lock/unlock details
FileLock fLock = lockMgr.createLockObject(m_sess, conn, netFile, new LockParams(offset, length, pid));
boolean isLock = lockIdx++ < lockCnt;
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.LOCK))
m_sess.debugPrintln(" " + (isLock ? "Lock" : "UnLock") + " lock=" + fLock);
// Perform the lock/unlock request
try {
// Check if the request is an unlock
if (isLock == false) {
// Unlock the file
lockMgr.unlockFile(m_sess, conn, netFile, fLock);
}
else {
// Lock the file
lockMgr.lockFile(m_sess, conn, netFile, fLock);
}
}
catch (NotLockedException ex) {
// Return an error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTRangeNotLocked, SMBStatus.DOSNotLocked, SMBStatus.ErrDos);
return;
}
catch (LockConflictException ex) {
// Return an error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTLockNotGranted, SMBStatus.DOSLockConflict, SMBStatus.ErrDos);
return;
}
catch (IOException ex) {
// Return an error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);
return;
}
}
}
else {
// Filesystem does not support byte range locking
//
// Return a 'not locked' status if there are unlocks in the request else return a
// success status
if (unlockCnt > 0) {
// Return an error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTRangeNotLocked, SMBStatus.DOSNotLocked, SMBStatus.ErrDos);
return;
}
}
// Return a success response
parser.setParameterCount(2);
parser.setAndXCommand(0xFF);
parser.setParameter(1, 0);
parser.setByteCount(0);
// Send the lock request response
m_sess.sendResponseSMB(smbPkt);
}
}
/**
* Process the logoff request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procLogoffAndX(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid logoff andX request
if (parser.checkPacketIsValid(2, 0) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
int uid = parser.getUserId();
VirtualCircuit vc = m_sess.findVirtualCircuit(uid);
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NEGOTIATE))
Debug.println("[SMB] LogoffAndX vc=" + vc);
// Mark the virtual circuit as logged off
vc.setLoggedOn(false);
// Check if there are no tree connections on this virtual circuit
if (vc.getConnectionCount() == 0) {
// Remove the virtual circuit
m_sess.removeVirtualCircuit(vc.getId());
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NEGOTIATE))
m_sess.debugPrintln(" Removed virtual circuit " + vc);
}
// Return a success status SMB
m_sess.sendSuccessResponseSMB(smbPkt);
// If there are no active virtual circuits then close the session/socket
if (m_sess.numberOfVirtualCircuits() == 0) {
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NEGOTIATE))
Debug.println(" Closing session, no more virtual circuits");
// Close the session/socket
m_sess.hangupSession("Client logoff");
}
}
/**
* Process the file open request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procOpenAndX(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid open andX request
if (parser.checkPacketIsValid(15, 1) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// If the connection is to the IPC$ remote admin named pipe pass the request to the IPC
// handler. If the device is not a disk type device then return an error.
if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
// Use the IPC$ handler to process the request
IPCHandler.processIPCRequest(m_sess, smbPkt);
return;
}
else if (conn.getSharedDevice().getType() != ShareType.DISK) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Extract the open file parameters
int flags = parser.getParameter(2);
int access = parser.getParameter(3);
int srchAttr = parser.getParameter(4);
int fileAttr = parser.getParameter(5);
int crTime = parser.getParameter(6);
int crDate = parser.getParameter(7);
int openFunc = parser.getParameter(8);
int allocSiz = parser.getParameterLong(9);
// Extract the filename string
String fileName = parser.unpackString(parser.isUnicode());
if (fileName == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Create the file open parameters
long crDateTime = 0L;
if (crTime > 0 && crDate > 0)
crDateTime = new SMBDate(crDate, crTime).getTime();
FileOpenParams params = new FileOpenParams(fileName, openFunc, access, srchAttr, fileAttr, allocSiz, crDateTime, parser.getProcessIdFull());
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("File Open AndX [" + treeId + "] params=" + params);
// Check if the file name is valid
if (FileName.isValidPath(params.getPath()) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Access the disk interface and open the requested file
int fid;
NetworkFile netFile = null;
int respAction = 0;
try {
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Check if the requested file already exists
FileStatus fileSts = disk.fileExists(m_sess, conn, fileName);
if (fileSts == FileStatus.NotExist) {
// Check if the file should be created if it does not exist
if (FileAction.createNotExists(openFunc)) {
// Check if the session has write access to the filesystem
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Create a new file
netFile = disk.createFile(m_sess, conn, params);
// Indicate that the file did not exist and was created
respAction = FileAction.FileCreated;
}
else {
// Check if the path is a directory
if (fileSts == FileStatus.DirectoryExists) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
}
else {
// Return a file not found error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
}
return;
}
}
else {
// Open the requested file
netFile = disk.openFile(m_sess, conn, params);
// Set the file action response
if (FileAction.truncateExistingFile(openFunc)) {
// Truncate the existing file
disk.truncateFile(m_sess, conn, netFile, 0L);
// Set the response
respAction = FileAction.FileTruncated;
}
else
respAction = FileAction.FileExisted;
}
// Add the file to the list of open files for this tree connection
fid = conn.addFile(netFile, getSession());
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
catch (TooManyFilesException ex) {
// Too many files are open on this connection, cannot open any more files.
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSTooManyOpenFiles, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (FileSharingException ex) {
// Return a sharing violation error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTSharingViolation, SMBStatus.DOSFileSharingConflict, SMBStatus.ErrDos);
return;
}
catch (FileOfflineException ex) {
// File data is unavailable
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTFileOffline, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (IOException ex) {
// Failed to open the file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
// Build the open file response
parser.setParameterCount(15);
parser.setAndXCommand(0xFF);
parser.setParameter(1, 0); // AndX offset
parser.setParameter(2, fid);
parser.setParameter(3, netFile.getFileAttributes()); // file attributes
SMBDate modDate = null;
if (netFile.hasModifyDate())
modDate = new SMBDate(netFile.getModifyDate());
parser.setParameter(4, modDate != null ? modDate.asSMBTime() : 0); // last write time
parser.setParameter(5, modDate != null ? modDate.asSMBDate() : 0); // last write date
parser.setParameterLong(6, netFile.getFileSizeInt()); // file size
parser.setParameter(8, netFile.getGrantedAccess().intValue());
parser.setParameter(9, OpenAndX.FileTypeDisk);
parser.setParameter(10, 0); // named pipe state
parser.setParameter(11, respAction);
parser.setParameter(12, 0); // server FID (long)
parser.setParameter(13, 0);
parser.setParameter(14, 0);
parser.setByteCount(0);
// Send the response packet
m_sess.sendResponseSMB(smbPkt);
}
/**
* Process the file read request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procReadAndX(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid read andX request
if (parser.checkPacketIsValid(10, 0) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// If the connection is to the IPC$ remote admin named pipe pass the request to the IPC handler.
if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
// Use the IPC$ handler to process the request
IPCHandler.processIPCRequest(m_sess, smbPkt);
return;
}
// Extract the read file parameters
int fid = parser.getParameter(2);
long offset = parser.getParameterLong(3); // bottom 32bits of read offset
offset &= 0xFFFFFFFFL;
int maxCount = parser.getParameter(5);
// Check for the NT format request that has the top 32bits of the file offset
if (parser.getParameterCount() == 12) {
long topOff = parser.getParameterLong(10);
offset += topOff << 32;
}
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILEIO))
m_sess.debugPrintln("File Read AndX [" + netFile.getFileId() + "] : Size=" + maxCount + " ,Pos=" + offset);
// Read data from the file
SMBSrvPacket respPkt = smbPkt;
byte[] buf = respPkt.getBuffer();
int dataPos = 0;
int rdlen = 0;
try {
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Set the returned parameter count so that the byte offset can be calculated
parser.setParameterCount(12);
dataPos = parser.getByteOffset();
dataPos = DataPacker.wordAlign(dataPos); // align the data buffer
// Check if the requested data will fit into the current packet
if (maxCount > (buf.length - dataPos)) {
// Allocate a larger packet for the response
respPkt = m_sess.getPacketPool().allocatePacket(maxCount + dataPos, smbPkt);
// Set the response parser
respPkt.setParser( SMBSrvPacket.Version.V1);
parser = (SMBV1Parser) respPkt.getParser();
// Switch to the response buffer
buf = respPkt.getBuffer();
parser.setParameterCount(12);
}
// Check if the requested data length will fit into the buffer
int dataLen = buf.length - dataPos;
if (dataLen < maxCount)
maxCount = dataLen;
// Read from the file
// Synchronize reads using the network file
synchronized (netFile) {
rdlen = disk.readFile(m_sess, conn, netFile, buf, dataPos, maxCount, offset);
}
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
catch (FileOfflineException ex) {
// File data is unavailable
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTFileOffline, SMBStatus.HRDReadFault, SMBStatus.ErrHrd);
return;
}
catch (LockConflictException ex) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.LOCK))
m_sess.debugPrintln("Read Lock Error [" + netFile.getFileId() + "] : Size=" + maxCount + " ,Pos=" + offset);
// File is locked
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTLockConflict, SMBStatus.DOSLockConflict, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// User does not have the required access rights or file is not accessible
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (DiskOfflineException ex) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILEIO))
m_sess.debugPrintln("Filesystem Offline Error [" + netFile.getFileId() + "] Read File");
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (IOException ex) {
// Debug
if (Debug.EnableError && m_sess.hasDebug(SMBSrvSession.Dbg.FILEIO)) {
m_sess.debugPrintln("File Read Error [" + netFile.getFileId() + "] : " + ex.toString());
m_sess.debugPrintln(ex);
// Dump the network file details
m_sess.debugPrintln(" NetworkFile name=" + netFile.getName() + "/" + netFile.getFullName());
m_sess.debugPrintln(" attr=0x" + Integer.toHexString(netFile.getFileAttributes()) + ", size=" + netFile.getFileSize());
m_sess.debugPrintln(" fid=" + netFile.getFileId() + ", cdate=" + netFile.getCreationDate() + ", mdate=" + netFile.getModifyDate());
m_sess.debugPrintln("Offset = " + offset + " (0x" + Long.toHexString(offset) + ")");
}
// Failed to read the file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTFileOffline, SMBStatus.HRDReadFault, SMBStatus.ErrHrd);
return;
}
// Return the data block
parser.setAndXCommand(0xFF); // no chained command
parser.setParameter(1, 0);
parser.setParameter(2, 0); // bytes remaining, for pipes only
parser.setParameter(3, 0); // data compaction mode
parser.setParameter(4, 0); // reserved
parser.setParameter(5, rdlen); // data length
parser.setParameter(6, dataPos - RFCNetBIOSProtocol.HEADER_LEN); // offset to data
// Clear the reserved parameters
for (int i = 7; i < 12; i++)
parser.setParameter(i, 0);
// Set the byte count
parser.setByteCount((dataPos + rdlen) - parser.getByteOffset());
// Check if there is a chained command, or commands
if (parser.hasAndXCommand()) {
// Process any chained commands, AndX
int pos = procAndXCommands(smbPkt, parser, netFile);
// Send the read andX response
m_sess.sendResponseSMB(smbPkt.getAssociatedPacket(), pos);
}
else {
// Send the normal read andX response
m_sess.sendResponseSMB(respPkt);
}
}
/**
* Rename a file.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected void procRenameFile(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid rename file request
if (parser.checkPacketIsValid(1, 4) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVUnrecognizedCommand, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree id from the received packet and validate that it is a valid
// connection id.
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNetworkAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the Unicode flag
boolean isUni = parser.isUnicode();
// Read the data block
parser.resetBytePointer();
// Extract the old file name
if (parser.unpackByte() != DataType.ASCII) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
String oldName = parser.unpackString(isUni);
if (oldName == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Extract the new file name
if (parser.unpackByte() != DataType.ASCII) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
String newName = parser.unpackString(isUni);
if (newName == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("File Rename [" + treeId + "] old name=" + oldName + ", new name=" + newName);
// Check if the from/to paths are valid
if (FileName.isValidPath(oldName) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
if (FileName.isValidPath(newName) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Access the disk interface and rename the requested file
int fid;
NetworkFile netFile = null;
try {
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Rename the requested file
disk.renameFile(m_sess, conn, oldName, newName, null);
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
catch (FileNotFoundException ex) {
// Source file/directory does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
catch (FileExistsException ex) {
// Destination file/directory already exists
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameCollision, SMBStatus.DOSFileAlreadyExists, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Target file/directory exists
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (PermissionDeniedException ex) {
// Not allowed to rename the file/directory
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNetworkAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (FileSharingException ex) {
// Return a sharing violation error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTSharingViolation, SMBStatus.DOSFileSharingConflict, SMBStatus.ErrDos);
return;
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
}
catch (IOException ex) {
// I/O exception
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Build the rename file response
parser.setParameterCount(0);
parser.setByteCount(0);
parser.setSuccessStatus();
// Send the response packet
m_sess.sendResponseSMB(smbPkt);
// Check if there are any file/directory change notify requests active
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (diskCtx.hasFileServerNotifications())
diskCtx.getChangeHandler().notifyRename(oldName, newName);
}
/**
* Delete a file.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected void procDeleteFile(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid file delete request
if (parser.checkPacketIsValid(1, 2) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVUnrecognizedCommand, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree id from the received packet and validate that it is a valid
// connection id.
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the Unicode flag
boolean isUni = parser.isUnicode();
// Read the data block
parser.resetBytePointer();
// Extract the old file name
if (parser.unpackByte() != DataType.ASCII) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
String fileName = parser.unpackString(isUni);
if (fileName == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("File Delete [" + treeId + "] name=" + fileName);
// Access the disk interface and delete the file(s)
int fid;
NetworkFile netFile = null;
long startTime = 0L;
try {
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.BENCHMARK))
startTime = System.currentTimeMillis();
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Delete file(s)
disk.deleteFile(m_sess, conn, fileName);
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.BENCHMARK))
Debug.println("Benchmark: Delete file " + fileName + " took " + (System.currentTimeMillis() - startTime) + "ms");
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Not allowed to delete the file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (IOException ex) {
// Failed to open the file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
// Build the delete file response
parser.setParameterCount(0);
parser.setByteCount(0);
parser.setSuccessStatus();
// Send the response packet
m_sess.sendResponseSMB(smbPkt);
// Check if there are any file/directory change notify requests active
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (diskCtx.hasFileServerNotifications())
diskCtx.getChangeHandler().notifyFileChanged(NotifyAction.Removed, fileName);
}
/**
* Delete a directory.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected void procDeleteDirectory(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid delete directory request
if (parser.checkPacketIsValid(0, 2) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVUnrecognizedCommand, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree id from the received packet and validate that it is a valid
// connection id.
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the Unicode flag
boolean isUni = parser.isUnicode();
// Read the data block
parser.resetBytePointer();
// Extract the old file name
if (parser.unpackByte() != DataType.ASCII) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
String dirName = parser.unpackString(isUni);
if (dirName == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("Directory Delete [" + treeId + "] name=" + dirName);
// Access the disk interface and delete the directory
try {
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Delete the directory
disk.deleteDirectory(m_sess, conn, dirName);
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Not allowed to delete the directory
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (DirectoryNotEmptyException ex) {
// Directory not empty
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTDirectoryNotEmpty, SMBStatus.DOSAccessDenied, SMBStatus.NTErr);
return;
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (IOException ex) {
// Failed to delete the directory
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSDirectoryInvalid, SMBStatus.ErrDos);
return;
}
// Build the delete directory response
parser.setParameterCount(0);
parser.setByteCount(0);
parser.setSuccessStatus();
// Send the response packet
m_sess.sendResponseSMB(smbPkt);
// Check if there are any file/directory change notify requests active
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (diskCtx.hasFileServerNotifications())
diskCtx.getChangeHandler().notifyDirectoryChanged(NotifyAction.Removed, dirName);
}
/**
* Process a transact2 file search request.
*
* @param tbuf Transaction request details
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procTrans2FindFirst(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoAccessRights, SMBStatus.ErrSrv);
return;
}
// Get the search parameters
DataBuffer paramBuf = tbuf.getParameterBuffer();
int srchAttr = paramBuf.getShort();
int maxFiles = paramBuf.getShort();
int srchFlag = paramBuf.getShort();
int infoLevl = paramBuf.getShort();
paramBuf.skipBytes(4);
String srchPath = paramBuf.getString(tbuf.isUnicode());
// Check if the search contains Unicode wildcards
if (tbuf.isUnicode() && WildCard.containsUnicodeWildcard(srchPath)) {
// Translate the Unicode wildcards to standard DOS wildcards
srchPath = WildCard.convertUnicodeWildcardToDOS(srchPath);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Converted Unicode wildcards to:" + srchPath);
}
// Check if the search path is valid
if (FileName.isValidSearchPath(srchPath) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Check if the search path is valid
if (srchPath == null || srchPath.length() == 0) {
// Invalid search request
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
else if (srchPath.endsWith(FileName.DOS_SEPERATOR_STR)) {
// Make the search a wildcard search
srchPath = srchPath + "*.*";
}
else if (srchPath.startsWith(FileName.DOS_SEPERATOR_STR) == false) {
// Prefix the search path to make it a relative path
srchPath = FileName.DOS_SEPERATOR_STR + srchPath;
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Search path missing leading slash, converted to relative path");
}
// Check for the Macintosh information level, if the Macintosh extensions are not enabled return an error
if (infoLevl == FindInfoPacker.InfoMacHfsInfo && getSession().hasMacintoshExtensions() == false) {
// Return an error status, Macintosh extensions are not enabled
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
return;
}
// Access the shared device disk interface
SearchContext ctx = null;
DiskInterface disk = null;
int searchId = -1;
boolean wildcardSearch = false;
try {
// Access the disk interface
disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Allocate a search slot for the new search
searchId = vc.allocateSearchSlot();
// Check if this is a wildcard search or single file search
if (WildCard.containsWildcards(srchPath))
wildcardSearch = true;
// Start a new search
ctx = disk.startSearch(m_sess, conn, srchPath, srchAttr, EnumSet.noneOf( SearchFlags.class));
if (ctx != null) {
// Store details of the search in the context
ctx.setTreeId(treeId);
ctx.setMaximumFiles(maxFiles);
}
else {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Failed to start the search, return a no more files error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNoSuchFile, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
// Save the search context
vc.setSearchContext(searchId, ctx);
// Create the reply transact buffer
SrvTransactBuffer replyBuf = new SrvTransactBuffer(tbuf);
DataBuffer dataBuf = replyBuf.getDataBuffer();
// Determine the maximum return data length
int maxLen = replyBuf.getReturnDataLimit();
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Start trans search [" + searchId + "] - " + srchPath + ", attr=0x"
+ Integer.toHexString(srchAttr) + ", maxFiles=" + maxFiles + ", maxLen=" + maxLen + ", infoLevel="
+ infoLevl + ", flags=0x" + Integer.toHexString(srchFlag) + ",dotFiles=" + ctx.hasDotFiles());
// Loop until we have filled the return buffer or there are no more files to return
int fileCnt = 0;
int packLen = 0;
int lastNameOff = 0;
// Flag to indicate if resume ids should be returned
boolean resumeIds = false;
if (infoLevl == FindInfoPacker.InfoStandard && (srchFlag & FindFirstNext.ReturnResumeKey) != 0) {
// Windows servers only seem to return resume keys for the standard information level
resumeIds = true;
}
// If this is a wildcard search then add the '.' and '..' entries
if (wildcardSearch == true && WildCard.isWildcardAll(srchPath) && ReturnDotFiles == true) {
// Pack the '.' file information
if (resumeIds == true) {
dataBuf.putInt(-1);
maxLen -= 4;
}
lastNameOff = dataBuf.getPosition();
// Check if the search has the '.' file entry details
FileInfo dotInfo = new FileInfo(".", 0, FileAttribute.Directory);
dotInfo.setFileId(dotInfo.getFileName().hashCode());
if (ctx.hasDotFiles())
ctx.getDotInfo(dotInfo);
packLen = FindInfoPacker.packInfo(dotInfo, dataBuf, infoLevl, tbuf.isUnicode());
// Update the file count for this packet, update the remaining buffer length
fileCnt++;
maxLen -= packLen;
// Pack the '..' file information
if (resumeIds == true) {
dataBuf.putInt(-2);
maxLen -= 4;
}
lastNameOff = dataBuf.getPosition();
// Check if the search has the '..' file entry details
if (ctx.hasDotFiles())
ctx.getDotDotInfo(dotInfo);
else {
// Set dummy details for the '..' file entry
dotInfo.setFileName("..");
dotInfo.setFileId(dotInfo.getFileName().hashCode());
dotInfo.setCreationDateTime(DotFileDateTime);
dotInfo.setModifyDateTime(DotFileDateTime);
dotInfo.setAccessDateTime(DotFileDateTime);
}
packLen = FindInfoPacker.packInfo(dotInfo, dataBuf, infoLevl, tbuf.isUnicode());
// Update the file count for this packet, update the remaining buffer length
fileCnt++;
maxLen -= packLen;
}
boolean pktDone = false;
boolean searchDone = false;
FileInfo info = new FileInfo();
while (pktDone == false && fileCnt < maxFiles) {
// Get file information from the search
if (ctx.nextFileInfo(info) == false) {
// No more files
pktDone = true;
searchDone = true;
}
// Check if the file information will fit into the return buffer
else if (FindInfoPacker.calcInfoSize(info, infoLevl, false, true) <= maxLen) {
// Pack the resume id, if required
if (resumeIds == true) {
dataBuf.putInt(ctx.getResumeId());
maxLen -= 4;
}
// Save the offset to the last file information structure
lastNameOff = dataBuf.getPosition();
// Pack the file information
packLen = FindInfoPacker.packInfo(info, dataBuf, infoLevl, tbuf.isUnicode());
// Update the file count for this packet
fileCnt++;
// Recalculate the remaining buffer space
maxLen -= packLen;
}
else {
// Set the search restart point
ctx.restartAt(info);
// No more buffer space
pktDone = true;
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Find first response full, restart at " + info.getFileName());
}
}
// Check for a single file search and the file was not found, in this case return an error status
if (fileCnt == 0)
throw new FileNotFoundException(srchPath);
// Check for a search where the maximum files is set to one, close the search immediately.
if (maxFiles == 1 && fileCnt == 1)
searchDone = true;
// Clear the next structure offset, if applicable
FindInfoPacker.clearNextOffset(dataBuf, infoLevl, lastNameOff);
// Pack the parameter block
paramBuf = replyBuf.getParameterBuffer();
paramBuf.putShort(searchId);
paramBuf.putShort(fileCnt);
paramBuf.putShort(ctx.hasMoreFiles() ? 0 : 1);
paramBuf.putShort(0);
paramBuf.putShort(lastNameOff);
// Send the transaction response
SMBSrvTransPacket tpkt = new SMBSrvTransPacket(smbPkt);
tpkt.doTransactionResponse(m_sess, replyBuf, smbPkt);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Search [" + searchId + "] Returned " + fileCnt + " files, dataLen=" + dataBuf.getLength()
+ ", moreFiles=" + ctx.hasMoreFiles());
// Check if the search is complete
if (searchDone == true || ctx.hasMoreFiles() == false) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("End start search [" + searchId + "] (Search complete)");
// Release the search context
vc.deallocateSearchSlot(searchId);
}
else if ((srchFlag & FindFirstNext.CloseSearch) != 0) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("End start search [" + searchId + "] (Close)");
// Release the search context
vc.deallocateSearchSlot(searchId);
}
}
catch ( TooManySearchesException ex) {
// Failed to allocate a slot for the new search
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNoResourcesAvailable, SMBStatus.ErrSrv);
}
catch (FileNotFoundException ex) {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Search path does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNoSuchFile, SMBStatus.DOSNoMoreFiles, SMBStatus.ErrDos);
}
catch (PathNotFoundException ex) {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Requested path does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
catch (InvalidDeviceInterfaceException ex) {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
}
catch (UnsupportedInfoLevelException ex) {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Requested information level is not supported
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidLevel, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
}
catch (DiskOfflineException ex) {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
}
}
/**
* Process a transact2 file search continue request.
*
* @param tbuf Transaction request details
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procTrans2FindNext(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the search parameters
DataBuffer paramBuf = tbuf.getParameterBuffer();
int searchId = paramBuf.getShort();
int maxFiles = paramBuf.getShort();
int infoLevl = paramBuf.getShort();
int reskey = paramBuf.getInt();
int srchFlag = paramBuf.getShort();
String resumeName = paramBuf.getString(tbuf.isUnicode());
// Access the shared device disk interface
SearchContext ctx = null;
DiskInterface disk = null;
try {
// Access the disk interface
disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Retrieve the search context
ctx = vc.getSearchContext(searchId);
if (ctx == null) {
// DEBUG
if (Debug.EnableError)
m_sess.debugPrintln("Search context null - [" + searchId + "]");
// Invalid search handle
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSNoMoreFiles, SMBStatus.ErrDos);
return;
}
// Create the reply transaction buffer
SrvTransactBuffer replyBuf = new SrvTransactBuffer(tbuf);
DataBuffer dataBuf = replyBuf.getDataBuffer();
// Determine the maximum return data length
int maxLen = replyBuf.getReturnDataLimit();
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Continue search [" + searchId + "] - " + resumeName + ", maxFiles=" + maxFiles + ", maxLen="
+ maxLen + ", infoLevel=" + infoLevl + ", flags=0x" + Integer.toHexString(srchFlag));
// Loop until we have filled the return buffer or there are no more files to return
int fileCnt = 0;
int packLen = 0;
int lastNameOff = 0;
// Flag to indicate if resume ids should be returned
boolean resumeIds = false;
if (infoLevl == FindInfoPacker.InfoStandard && (srchFlag & FindFirstNext.ReturnResumeKey) != 0) {
// Windows servers only seem to return resume keys for the standard information level
resumeIds = true;
}
// Flags to indicate packet full or search complete
boolean pktDone = false;
boolean searchDone = false;
FileInfo info = new FileInfo();
while (pktDone == false && fileCnt < maxFiles) {
// Get file information from the search
if (ctx.nextFileInfo(info) == false) {
// No more files
pktDone = true;
searchDone = true;
}
// Check if the file information will fit into the return buffer
else if (FindInfoPacker.calcInfoSize(info, infoLevl, false, true) <= maxLen) {
// Pack the resume id, if required
if (resumeIds == true) {
dataBuf.putInt(ctx.getResumeId());
maxLen -= 4;
}
// Save the offset to the last file information structure
lastNameOff = dataBuf.getPosition();
// Pack the file information
packLen = FindInfoPacker.packInfo(info, dataBuf, infoLevl, tbuf.isUnicode());
// Update the file count for this packet
fileCnt++;
// Recalculate the remaining buffer space
maxLen -= packLen;
}
else {
// Set the search restart point
ctx.restartAt(info);
// No more buffer space
pktDone = true;
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Find next response full, restart at " + info.getFileName());
}
}
// Pack the parameter block
paramBuf = replyBuf.getParameterBuffer();
paramBuf.putShort(fileCnt);
paramBuf.putShort(ctx.hasMoreFiles() ? 0 : 1);
paramBuf.putShort(0);
paramBuf.putShort(lastNameOff);
// Send the transaction response
SMBSrvTransPacket tpkt = new SMBSrvTransPacket(smbPkt);
tpkt.doTransactionResponse(m_sess, replyBuf, smbPkt);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("Search [" + searchId + "] Returned " + fileCnt + " files, dataLen=" + dataBuf.getLength()
+ ", moreFiles=" + ctx.hasMoreFiles());
// Check if the search is complete
if (searchDone == true || ctx.hasMoreFiles() == false) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("End start search [" + searchId + "] (Search complete)");
// Release the search context
vc.deallocateSearchSlot(searchId);
}
else if ((srchFlag & FindFirstNext.CloseSearch) != 0) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.SEARCH))
m_sess.debugPrintln("End start search [" + searchId + "] (Close)");
// Release the search context
vc.deallocateSearchSlot(searchId);
}
}
catch (FileNotFoundException ex) {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Search path does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSNoMoreFiles, SMBStatus.ErrDos);
}
catch (InvalidDeviceInterfaceException ex) {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
}
catch (UnsupportedInfoLevelException ex) {
// Deallocate the search
if (searchId != -1)
vc.deallocateSearchSlot(searchId);
// Requested information level is not supported
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
}
}
/**
* Process a transact2 file system query request.
*
* @param tbuf Transaction request details
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procTrans2QueryFileSys(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the query file system required information level
DataBuffer paramBuf = tbuf.getParameterBuffer();
int infoLevl = paramBuf.getShort();
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln("Query File System Info - level = 0x" + Integer.toHexString(infoLevl));
// Access the shared device disk interface
try {
// Access the disk interface and context
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
// Set the return parameter count, so that the data area position can be calculated.
parser.setParameterCount(10);
// Pack the disk information into the data area of the transaction reply
byte[] buf = parser.getBuffer();
int prmPos = DataPacker.longwordAlign(parser.getByteOffset());
int dataPos = prmPos; // no parameters returned
// Create a data buffer using the SMB packet. The response should always fit into a
// single reply packet.
DataBuffer replyBuf = new DataBuffer(buf, dataPos, buf.length - dataPos);
// Determine the information level requested
SrvDiskInfo diskInfo = null;
VolumeInfo volInfo = null;
switch (infoLevl) {
// Standard disk information
case DiskInfoPacker.InfoStandard:
// Get the disk information
diskInfo = getDiskInformation(disk, diskCtx);
// Pack the disk information into the return data packet
DiskInfoPacker.packStandardInfo(diskInfo, replyBuf);
break;
// Volume label information
case DiskInfoPacker.InfoVolume:
// Get the volume label information
volInfo = getVolumeInformation(disk, diskCtx);
// Pack the volume label information
DiskInfoPacker.packVolumeInfo(volInfo, replyBuf, tbuf.isUnicode());
break;
// Full volume information
case DiskInfoPacker.InfoFsVolume:
// Get the volume information
volInfo = getVolumeInformation(disk, diskCtx);
// Pack the volume information
DiskInfoPacker.packFsVolumeInformation(volInfo, replyBuf, tbuf.isUnicode());
break;
// Filesystem size information
case DiskInfoPacker.InfoFsSize:
// Get the disk information
diskInfo = getDiskInformation(disk, diskCtx);
// Pack the disk information into the return data packet
DiskInfoPacker.packFsSizeInformation(diskInfo, replyBuf);
break;
// Filesystem device information
case DiskInfoPacker.InfoFsDevice:
DiskInfoPacker.packFsDevice(NTIOCtl.DeviceDisk, diskCtx.getDeviceAttributes(), replyBuf);
break;
// Filesystem attribute information
case DiskInfoPacker.InfoFsAttribute:
String fsType = diskCtx.getFilesystemType();
if (disk instanceof NTFSStreamsInterface) {
// Check if NTFS streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
if (ntfsStreams.hasStreamsEnabled(m_sess, conn))
fsType = "NTFS";
}
// Pack the filesystem type
DiskInfoPacker.packFsAttribute(diskCtx.getFilesystemAttributes(), MaxPathLength, fsType, tbuf.isUnicode(),
replyBuf);
break;
// Mac filesystem information
case DiskInfoPacker.InfoMacFsInfo:
// Check if the filesystem supports NTFS streams
//
// We should only return a valid response to the Macintosh information level if the
// filesystem does NOT support NTFS streams. By returning an error status the Thursby DAVE
// software will treat the filesystem as a WinXP/2K filesystem with full streams support.
boolean ntfs = false;
if (disk instanceof NTFSStreamsInterface) {
// Check if streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
ntfs = ntfsStreams.hasStreamsEnabled(m_sess, conn);
}
// If the filesystem does not support NTFS streams then send a valid response.
if (ntfs == false) {
// Get the disk and volume information
diskInfo = getDiskInformation(disk, diskCtx);
volInfo = getVolumeInformation(disk, diskCtx);
// Pack the disk information into the return data packet
DiskInfoPacker.packMacFsInformation(diskInfo, volInfo, ntfs, replyBuf);
}
break;
// Filesystem size information, including per user allocation limit
case DiskInfoPacker.InfoFullFsSize:
// Get the disk information
diskInfo = getDiskInformation(disk, diskCtx);
// Check if there is a quota manager configured, if so then get the per user free
// space from the quota manager
long userLimit = -1L;
long userTotalSpace = -1L;
if (diskCtx.hasQuotaManager()) {
// Get the per user quota and free space from the quota manager
userTotalSpace = diskCtx.getQuotaManager().getUserTotalSpace(m_sess, conn);
userLimit = diskCtx.getQuotaManager().getUserFreeSpace(m_sess, conn);
}
// If the per user free space is not valid then use the total available free space,
// else convert to allocation units.
if (userTotalSpace > 0)
userTotalSpace = userTotalSpace / diskInfo.getUnitSize();
else
userTotalSpace = diskInfo.getTotalUnits();
if (userLimit != -1L)
userLimit = userLimit / diskInfo.getUnitSize();
else
userLimit = diskInfo.getFreeUnits();
// Pack the disk information into the return data packet
DiskInfoPacker.packFullFsSizeInformation(userTotalSpace, userLimit, diskInfo, replyBuf);
break;
}
// Check if any data was packed, if not then the information level is not supported
if (replyBuf.getPosition() == dataPos) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
return;
}
int bytCnt = replyBuf.getPosition() - parser.getByteOffset();
replyBuf.setEndOfBuffer();
int dataLen = replyBuf.getLength();
SMBSrvTransPacket.initTransactReply(smbPkt, 0, prmPos, dataLen, dataPos);
parser.setByteCount(bytCnt);
// Send the transact reply
m_sess.sendResponseSMB(smbPkt);
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
}
/**
* Process a transact2 query path information request.
*
* @param tbuf Transaction request details
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procTrans2QueryPath(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the query path information level and file/directory name
DataBuffer paramBuf = tbuf.getParameterBuffer();
int infoLevl = paramBuf.getShort();
paramBuf.skipBytes(4);
String path = paramBuf.getString(tbuf.isUnicode());
if (path.length() == 0)
path = FileName.DOS_SEPERATOR_STR;
// Normalize paths that end with the NTFS data stream name
if (path.endsWith(FileName.DataStreamName))
path = path.substring(0, path.length() - FileName.DataStreamName.length());
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln("Query Path - level = 0x" + Integer.toHexString(infoLevl) + ", path = " + path);
// Access the shared device disk interface
try {
// Access the disk interface
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Set the return parameter count, so that the data area position can be calculated.
parser.setParameterCount(10);
// Pack the file information into the data area of the transaction reply
byte[] buf = parser.getBuffer();
int prmPos = DataPacker.longwordAlign(parser.getByteOffset());
int dataPos = prmPos + 4;
// Pack the return parametes, EA error offset
parser.setPosition(prmPos);
parser.packWord(0);
// Create a data buffer for the file information
DataBuffer replyBuf = new DataBuffer(256);
// Check if the virtual filesystem supports streams, and streams are enabled
boolean streams = false;
if (disk instanceof NTFSStreamsInterface) {
// Check if NTFS streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
streams = ntfsStreams.hasStreamsEnabled(m_sess, conn);
}
// Check if the path is for an NTFS stream, return an error if streams are not supported
// or not enabled
if (streams == false && path.indexOf(FileOpenParams.StreamSeparator) != -1) {
// NTFS streams not supported, return an error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
// Check for the file streams information level
int dataLen = 0;
if (streams == true && (infoLevl == FileInfoLevel.PathFileStreamInfo || infoLevl == FileInfoLevel.NTFileStreamInfo)) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.STREAMS))
m_sess.debugPrintln("Get NTFS streams list path=" + path);
// Get the list of streams from the share driver
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
StreamInfoList streamList = ntfsStreams.getStreamList(m_sess, conn, path);
if (streamList == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNoSuchFile, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
// Pack the file streams information into the return data packet
dataLen = QueryInfoPacker.packStreamFileInfo(streamList, replyBuf, true);
}
else {
// Get the file information
FileInfo fileInfo = disk.getFileInformation(m_sess, conn, path);
if (fileInfo == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNoSuchFile, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
// Pack the file information into the return data packet
dataLen = QueryInfoPacker.packInfo(fileInfo, replyBuf, infoLevl, true);
}
// Check if any data was packed, if not then the information level is not supported
if (dataLen == 0) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Check if the file information response will fit into the current packet
SMBSrvPacket respPkt = smbPkt;
SMBSrvTransPacket.initTransactReply(respPkt, 2, prmPos, dataLen, dataPos);
if (parser.getAvailableLength() < (dataLen + 4)) {
// Allocate a new buffer for the response
respPkt = m_sess.getPacketPool().allocatePacket(parser.getByteOffset() + dataLen + 4, smbPkt, parser.getByteOffset());
// Set the parser for the response packet
respPkt.setParser( SMBSrvPacket.Version.V1);
parser = (SMBV1Parser) respPkt.getParser();
}
// Copy the file information to the response packet
replyBuf.setEndOfBuffer();
replyBuf.copyData(respPkt.getBuffer(), dataPos);
// Set the byte count
parser.setByteCount((dataPos + dataLen) - parser.getByteOffset());
// Send the transact reply
m_sess.sendResponseSMB(respPkt);
}
catch (FileNotFoundException ex) {
// Requested file does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNoSuchFile, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
catch (PathNotFoundException ex) {
// Requested path does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
catch (UnsupportedInfoLevelException ex) {
// Requested information level is not supported
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (AccessDeniedException ex) {
// access denied
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
}
/**
* Process a transact2 query file information (via handle) request.
*
* @param tbuf Transaction request details
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procTrans2QueryFile(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the file id and query path information level
DataBuffer paramBuf = tbuf.getParameterBuffer();
int fid = paramBuf.getShort();
int infoLevl = paramBuf.getShort();
// Get the file details via the file id
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln("Query File - level=0x" + Integer.toHexString(infoLevl) + ", fid=" + fid + ", stream="
+ netFile.getStreamId() + ", name=" + netFile.getFullName());
// Access the shared device disk interface
try {
// Access the disk interface
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Set the return parameter count, so that the data area position can be calculated.
parser.setParameterCount(10);
// Pack the file information into the data area of the transaction reply
byte[] buf = parser.getBuffer();
int prmPos = DataPacker.longwordAlign(parser.getByteOffset());
int dataPos = prmPos + 4;
// Pack the return parametes, EA error offset
parser.setPosition(prmPos);
parser.packWord(0);
// Check if the virtual filesystem supports streams, and streams are enabled
boolean streams = false;
DataBuffer replyBuf = null;
if (disk instanceof NTFSStreamsInterface) {
// Check if NTFS streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
streams = ntfsStreams.hasStreamsEnabled(m_sess, conn);
}
// Check for the file streams information level
int dataLen = 0;
if (streams == true && (infoLevl == FileInfoLevel.PathFileStreamInfo || infoLevl == FileInfoLevel.NTFileStreamInfo)) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.STREAMS))
m_sess.debugPrintln("Get NTFS streams list fid=" + fid + ", name=" + netFile.getFullName());
// Get the list of streams from the share driver
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
StreamInfoList streamList = ntfsStreams.getStreamList(m_sess, conn, netFile.getFullName());
if (streamList == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Allocate a larger response buffer if there is more than one stream to return information for
if (streamList.numberOfStreams() > 1 && buf.length < NTFSStreamsInfoBufsize) {
// Allocate a larger packet for the response
smbPkt = m_sess.getPacketPool().allocatePacket(NTFSStreamsInfoBufsize, smbPkt, dataPos);
// Switch to the response buffer
buf = parser.getBuffer();
}
// Create a data buffer using the SMB packet. The response should always fit into a single reply packet.
replyBuf = new DataBuffer(buf, dataPos, buf.length - dataPos);
// Pack the file streams information into the return data packet
dataLen = QueryInfoPacker.packStreamFileInfo(streamList, replyBuf, true);
}
else {
// Get the file information
FileInfo fileInfo = disk.getFileInformation(m_sess, conn, netFile.getFullName());
if (fileInfo == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Copy current file size and access date/time from the open file
fileInfo.setFileSize(netFile.getFileSize());
fileInfo.setAllocationSize( MemorySize.roundupLongSize(fileInfo.getSize()));
if (netFile.hasAccessDate())
fileInfo.setAccessDateTime(netFile.getAccessDate());
// Create a data buffer using the SMB packet. The response should always fit into a
// single reply packet.
replyBuf = new DataBuffer(buf, dataPos, buf.length - dataPos);
// Pack the file information into the return data packet
dataLen = QueryInfoPacker.packInfo(fileInfo, replyBuf, infoLevl, true);
}
// Check if any data was packed, if not then the information level is not supported
if (dataLen == 0) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
SMBSrvTransPacket.initTransactReply(smbPkt, 2, prmPos, dataLen, dataPos);
parser.setByteCount(replyBuf.getPosition() - parser.getByteOffset());
// Send the transact reply
m_sess.sendResponseSMB(smbPkt);
}
catch (FileNotFoundException ex) {
// Requested file does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
catch (PathNotFoundException ex) {
// Requested path does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
catch (UnsupportedInfoLevelException ex) {
// Requested information level is not supported
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
}
}
/**
* Process a transact2 set file information (via handle) request.
*
* @param tbuf Transaction request details
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procTrans2SetFile(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the file id and information level
DataBuffer paramBuf = tbuf.getParameterBuffer();
int fid = paramBuf.getShort();
int infoLevl = paramBuf.getShort();
// Get the file details via the file id
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln("Set File - level=0x" + Integer.toHexString(infoLevl) + ", fid=" + fid + ", name="
+ netFile.getFullName());
// Access the shared device disk interface
try {
// Access the disk interface
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Process the set file information request
DataBuffer dataBuf = tbuf.getDataBuffer();
FileInfo finfo = null;
switch (infoLevl) {
// Set basic file information (dates/attributes)
case FileInfoLevel.SetBasicInfo:
case FileInfoLevel.NTFileBasicInfo:
// Create the file information template
int setFlags = 0;
finfo = new FileInfo(netFile.getFullName(), 0, -1);
// Set the creation date/time, if specified
long timeNow = System.currentTimeMillis();
long nttim = dataBuf.getLong();
boolean hasSetTime = false;
if (nttim != 0L) {
if (nttim != -1L) {
finfo.setCreationDateTime(NTTime.toJavaDate(nttim));
setFlags += FileInfo.SetCreationDate;
}
hasSetTime = true;
}
// Set the last access date/time, if specified
nttim = dataBuf.getLong();
if (nttim != 0L) {
if (nttim != -1L) {
finfo.setAccessDateTime(NTTime.toJavaDate(nttim));
setFlags += FileInfo.SetAccessDate;
}
else {
finfo.setAccessDateTime(timeNow);
setFlags += FileInfo.SetAccessDate;
}
hasSetTime = true;
}
// Set the last write date/time, if specified
nttim = dataBuf.getLong();
if (nttim > 0L) {
if (nttim != -1L) {
finfo.setModifyDateTime(NTTime.toJavaDate(nttim));
setFlags += FileInfo.SetModifyDate;
}
else {
finfo.setModifyDateTime(timeNow);
setFlags += FileInfo.SetModifyDate;
}
hasSetTime = true;
}
// Set the modify date/time, if specified
nttim = dataBuf.getLong();
if (nttim > 0L) {
if (nttim != -1L) {
finfo.setChangeDateTime(NTTime.toJavaDate(nttim));
setFlags += FileInfo.SetChangeDate;
}
hasSetTime = true;
}
// Set the attributes
int attr = dataBuf.getInt();
int unknown = dataBuf.getInt();
if (hasSetTime == false && unknown == 0) {
finfo.setFileAttributes(attr);
setFlags += FileInfo.SetAttributes;
}
// Store the associated network file in the file information object
finfo.setNetworkFile(netFile);
// Set the file information for the specified file/directory
finfo.setFileInformationFlags(setFlags);
disk.setFileInformation(m_sess, conn, netFile.getFullName(), finfo);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln(" Set Basic Info [" + treeId + "] name=" + netFile.getFullName() + ", attr=0x"
+ Integer.toHexString(attr) + ", setTime=" + hasSetTime + ", setFlags=0x"
+ Integer.toHexString(setFlags) + ", unknown=" + unknown);
break;
// Set end of file position for a file
case FileInfoLevel.SetEndOfFileInfo:
case FileInfoLevel.NTSetEndOfFileInfo:
// Get the new end of file position
long eofPos = dataBuf.getLong();
// Set the new end of file position
disk.truncateFile(m_sess, conn, netFile, eofPos);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln(" Set end of file position fid=" + fid + ", eof=" + eofPos);
break;
// Set the allocation size for a file
case FileInfoLevel.SetAllocationInfo:
case FileInfoLevel.NTSetFileAllocationInfo:
// Get the new end of file position
long allocSize = dataBuf.getLong();
// Set the new end of file position
disk.truncateFile(m_sess, conn, netFile, allocSize);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln(" Set allocation size fid=" + fid + ", allocSize=" + allocSize);
break;
// Rename a stream
case FileInfoLevel.NTFileRenameInfo:
// Unpack the rename details
boolean overwrite = dataBuf.getByte() == 1 ? true : false;
dataBuf.skipBytes(3);
int rootFid = dataBuf.getInt();
int nameLen = dataBuf.getInt();
String newName = dataBuf.getString(nameLen, true);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln(" Set rename fid=" + fid + ", newName=" + newName + ", overwrite=" + overwrite
+ ", rootFID=" + rootFid);
// Check if the new path contains a directory, only rename of a stream on the same file is supported.
// Make sure the network file is not a folder.
if (newName.indexOf(FileName.DOS_SEPERATOR_STR) != -1 || netFile.isDirectory()) {
// Return a not supported error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNotSupported, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
return;
}
// Check if the virtual filesystem supports streams, and streams are enabled
boolean streams = false;
if (disk instanceof NTFSStreamsInterface) {
// Check if NTFS streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
streams = ntfsStreams.hasStreamsEnabled(m_sess, conn);
}
// If streams are not supported or are not enabled then check for rename of a file/folder, or
// return an error status
if (streams == false) {
// Check if this is a rename of a file rather than a stream
if (FileName.containsStreamName(newName) == false) {
// Build the target file relative path
String[] paths = FileName.splitPath(netFile.getFullName());
String newPath = null;
if (paths[0] != null)
newPath = paths[0] + FileName.DOS_SEPERATOR_STR + newName;
else
newPath = FileName.DOS_SEPERATOR_STR + newName;
// Check if the target file exists
FileStatus fileSts = disk.fileExists(m_sess, conn, newPath);
if (fileSts == FileStatus.FileExists && overwrite == false) {
// Return an error status, rename would overwrite an existing file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
else {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("Transact rename via standard rename from=" + netFile.getFullName() + " to=" + newPath);
// Call the standard disk interface rename method to rename the file
disk.renameFile(m_sess, conn, netFile.getFullName(), newPath, null);
}
}
else {
// Return a not supported error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNotSupported, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
return;
}
}
else {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.STREAMS))
m_sess.debugPrintln("Rename stream fid=" + fid + ", name=" + netFile.getFullNameStream() + ", newName="
+ newName + ", overwrite=" + overwrite);
// Rename the stream
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
ntfsStreams.renameStream(m_sess, conn, netFile.getFullNameStream(), newName, overwrite);
}
break;
// Mark or unmark a file/directory for delete
case FileInfoLevel.SetDispositionInfo:
case FileInfoLevel.NTFileDispositionInfo:
// Get the delete flag
int flag = dataBuf.getByte();
boolean delFlag = flag == 1 ? true : false;
// Call the filesystem driver set file information to see if the file can be marked for delete.
FileInfo delInfo = new FileInfo();
delInfo.setDeleteOnClose(delFlag);
delInfo.setFileInformationFlags(FileInfo.SetDeleteOnClose);
disk.setFileInformation(m_sess, conn, netFile.getFullName(), delInfo);
// Mark/unmark the file/directory for deletion
netFile.setDeleteOnClose(delFlag);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln(" Set file disposition fid=" + fid + ", name=" + netFile.getName() + ", delete="
+ delFlag);
break;
}
// Set the return parameter count, so that the data area position can be calculated.
parser.setParameterCount(10);
// Pack the return information into the data area of the transaction reply
byte[] buf = parser.getBuffer();
int prmPos = parser.getByteOffset();
// Longword align the parameters, return an unknown word parameter
//
// Note: Make sure the data offset is on a longword boundary, NT has problems if this is
// not done
prmPos = DataPacker.longwordAlign(prmPos);
DataPacker.putIntelShort(0, buf, prmPos);
SMBSrvTransPacket.initTransactReply(smbPkt, 2, prmPos, 0, prmPos + 4);
parser.setByteCount((prmPos - parser.getByteOffset()) + 4);
// Send the transact reply
m_sess.sendResponseSMB(smbPkt);
// Check if there are any file/directory change notify requests active
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (diskCtx.hasFileServerNotifications() && netFile.getFullName() != null) {
// Get the change handler
NotifyChangeHandler changeHandler = diskCtx.getChangeHandler();
// Check for file attributes and last write time changes
if (finfo != null) {
// File attributes changed
if (finfo.hasSetFlag(FileInfo.SetAttributes))
changeHandler.notifyAttributesChanged(netFile.getFullName(), netFile.isDirectory());
// Last write time changed
if (finfo.hasSetFlag(FileInfo.SetModifyDate))
changeHandler.notifyLastWriteTimeChanged(netFile.getFullName(), netFile.isDirectory());
}
else if (infoLevl == FileInfoLevel.SetAllocationInfo || infoLevl == FileInfoLevel.SetEndOfFileInfo) {
// File size changed
changeHandler.notifyFileSizeChanged(netFile.getFullName());
}
}
}
catch (FileNotFoundException ex) {
// Requested file does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
}
catch (PermissionDeniedException ex) {
// Not allowed to rename the file/directory
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNetworkAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Not allowed to change file attributes/settings
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
}
catch (DiskFullException ex) {
// Disk is full
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTDiskFull, SMBStatus.HRDWriteFault, SMBStatus.ErrHrd);
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
}
catch (DirectoryNotEmptyException ex) {
// Directory not empty
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSDirectoryNotEmpty, SMBStatus.ErrDos);
}
catch (Exception ex) {
// Other error during set file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
}
}
/**
* Process a transact2 set path information request.
*
* @param tbuf Transaction request details
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procTrans2SetPath(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the path and information level
DataBuffer paramBuf = tbuf.getParameterBuffer();
int infoLevl = paramBuf.getShort();
paramBuf.skipBytes(4);
String path = paramBuf.getString(tbuf.isUnicode());
if (path.length() == 0)
path = FileName.DOS_SEPERATOR_STR;
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln("Set Path - path=" + path + ", level=0x" + Integer.toHexString(infoLevl));
// Check if the file name is valid
if (FileName.isValidPath(path) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Access the shared device disk interface
try {
// Access the disk interface
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Process the set file information request
DataBuffer dataBuf = tbuf.getDataBuffer();
FileInfo finfo = null;
int setFlags = 0;
int attr = 0;
switch (infoLevl) {
// Set standard file information (dates/attributes)
case FileInfoLevel.SetStandard:
// Create the file information template
finfo = new FileInfo(path, 0, -1);
// Set the creation date/time, if specified
int smbDate = dataBuf.getShort();
int smbTime = dataBuf.getShort();
boolean hasSetTime = false;
if (smbDate != 0 && smbTime != 0) {
finfo.setCreationDateTime(new SMBDate(smbDate, smbTime).getTime());
setFlags += FileInfo.SetCreationDate;
hasSetTime = true;
}
// Set the last access date/time, if specified
smbDate = dataBuf.getShort();
smbTime = dataBuf.getShort();
if (smbDate != 0 && smbTime != 0) {
finfo.setAccessDateTime(new SMBDate(smbDate, smbTime).getTime());
setFlags += FileInfo.SetAccessDate;
hasSetTime = true;
}
// Set the last write date/time, if specified
smbDate = dataBuf.getShort();
smbTime = dataBuf.getShort();
if (smbDate != 0 && smbTime != 0) {
finfo.setModifyDateTime(new SMBDate(smbDate, smbTime).getTime());
setFlags += FileInfo.SetModifyDate;
hasSetTime = true;
}
// Set the file size/allocation size
int fileSize = dataBuf.getInt();
if (fileSize != 0) {
finfo.setFileSize(fileSize);
setFlags += FileInfo.SetFileSize;
}
fileSize = dataBuf.getInt();
if (fileSize != 0) {
finfo.setAllocationSize(fileSize);
setFlags += FileInfo.SetAllocationSize;
}
// Set the attributes
attr = dataBuf.getInt();
int eaListLen = dataBuf.getInt();
if (hasSetTime == false && eaListLen == 0) {
finfo.setFileAttributes(attr);
setFlags += FileInfo.SetAttributes;
}
// Set the file information for the specified file/directory
finfo.setFileInformationFlags(setFlags);
disk.setFileInformation(m_sess, conn, path, finfo);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln(" Set Standard Info [" + treeId + "] name=" + path + ", attr=0x"
+ Integer.toHexString(attr) + ", setTime=" + hasSetTime + ", setFlags=0x"
+ Integer.toHexString(setFlags) + ", eaListLen=" + eaListLen);
break;
// Set basic file information (dates/attributes)
case FileInfoLevel.SetBasicInfo:
// Create the file information template
finfo = new FileInfo(path, 0, -1);
// Set the creation date/time, if specified
long dateTime = NTTime.toJavaDate(dataBuf.getLong());
if (dateTime != 0L) {
finfo.setCreationDateTime(dateTime);
setFlags += FileInfo.SetCreationDate;
}
// Set the last access date/time, if specified
dateTime = NTTime.toJavaDate(dataBuf.getLong());
if (dateTime != 0L) {
finfo.setAccessDateTime(dateTime);
setFlags += FileInfo.SetAccessDate;
}
// Set the last write date/time, if specified
dateTime = NTTime.toJavaDate(dataBuf.getLong());
if (dateTime != 0L) {
finfo.setModifyDateTime(dateTime);
setFlags += FileInfo.SetModifyDate;
}
// Set the change write date/time, if specified
dateTime = NTTime.toJavaDate(dataBuf.getLong());
if (dateTime != 0L) {
finfo.setChangeDateTime(dateTime);
setFlags += FileInfo.SetChangeDate;
}
// Set the attributes
attr = dataBuf.getInt();
if (attr != 0) {
finfo.setFileAttributes(attr);
setFlags += FileInfo.SetAttributes;
}
// Set the file information for the specified file/directory
finfo.setFileInformationFlags(setFlags);
disk.setFileInformation(m_sess, conn, path, finfo);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.INFO))
m_sess.debugPrintln(" Set Basic Info [" + treeId + "] name=" + path + ", attr=0x"
+ Integer.toHexString(attr) + ", setFlags=0x" + Integer.toHexString(setFlags));
break;
}
// Set the return parameter count, so that the data area position can be calculated.
parser.setParameterCount(10);
// Pack the return information into the data area of the transaction reply
byte[] buf = parser.getBuffer();
int prmPos = parser.getByteOffset();
// Longword align the parameters, return an unknown word parameter
//
// Note: Make sure the data offset is on a longword boundary, NT has problems if this is not done
prmPos = DataPacker.longwordAlign(prmPos);
DataPacker.putIntelShort(0, buf, prmPos);
SMBSrvTransPacket.initTransactReply(smbPkt, 2, prmPos, 0, prmPos + 4);
parser.setByteCount((prmPos - parser.getByteOffset()) + 4);
// Send the transact reply
m_sess.sendResponseSMB(smbPkt);
// Check if there are any file/directory change notify requests active
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (diskCtx.hasFileServerNotifications() && path != null) {
// Get the change handler
NotifyChangeHandler changeHandler = diskCtx.getChangeHandler();
// Check for file attributes and last write time changes
if (finfo != null) {
// Check if the path refers to a file or directory
FileStatus fileSts = disk.fileExists(m_sess, conn, path);
// File attributes changed
if (finfo.hasSetFlag(FileInfo.SetAttributes))
changeHandler.notifyAttributesChanged(path, fileSts == FileStatus.DirectoryExists ? true : false);
// Last write time changed
if (finfo.hasSetFlag(FileInfo.SetModifyDate))
changeHandler.notifyLastWriteTimeChanged(path, fileSts == FileStatus.DirectoryExists ? true : false);
}
}
}
catch (FileNotFoundException ex) {
// Requested file does not exist
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
}
catch (AccessDeniedException ex) {
// Not allowed to change file attributes/settings
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
}
catch (DiskFullException ex) {
// Disk is full
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTDiskFull, SMBStatus.HRDWriteFault, SMBStatus.ErrHrd);
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
}
catch (Exception ex) {
// Other error during set file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
}
}
/**
* Process the file write request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procWriteAndX(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid write andX request
if (parser.checkPacketIsValid(12, 0) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// If the connection is to the IPC$ remote admin named pipe pass the request to the IPC handler
if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
// Use the IPC$ handler to process the request
IPCHandler.processIPCRequest(m_sess, smbPkt);
return;
}
// Extract the write file parameters
int fid = parser.getParameter(2);
// Bottom 32bits of file offset
long offset = (long) (((long) parser.getParameterLong(3)) & 0xFFFFFFFFL);
int dataPos = parser.getParameter(11) + RFCNetBIOSProtocol.HEADER_LEN;
int dataLen = parser.getParameter(10);
int dataLenHigh = 0;
if (smbPkt.getReceivedLength() > 0xFFFF)
dataLenHigh = parser.getParameter(9) & 0x0001;
if (dataLenHigh > 0)
dataLen += (dataLenHigh << 16);
// Check for the NT format request that has the top 32bits of the file offset
if (parser.getParameterCount() == 14) {
long topOff = (long) (((long) parser.getParameterLong(12)) & 0xFFFFFFFFL);
offset += topOff << 32;
}
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidHandle, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILEIO))
m_sess.debugPrintln("File Write AndX [" + netFile.getFileId() + "] : Size=" + dataLen + " ,Pos=" + offset);
// Write data to the file
byte[] buf = parser.getBuffer();
int wrtlen = 0;
// Access the disk interface and write to the file
try {
// Access the disk interface that is associated with the shared device
DiskInterface disk = (DiskInterface) conn.getSharedDevice().getInterface();
// Synchronize writes using the network file
synchronized (netFile) {
// Write to the file
wrtlen = disk.writeFile(m_sess, conn, netFile, buf, dataPos, dataLen, offset);
}
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILEIO))
m_sess.debugPrintln("File Write Error [" + netFile.getFileId() + "] : " + ex.toString());
// Not allowed to write to the file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (LockConflictException ex) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.LOCK))
m_sess.debugPrintln("Write Lock Error [" + netFile.getFileId() + "] : Size=" + dataLen + " ,Pos=" + offset);
// File is locked
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTLockConflict, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (DiskFullException ex) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILEIO))
m_sess.debugPrintln("Write Quota Error [" + netFile.getFileId() + "] Disk full : Size=" + dataLen + " ,Pos="
+ offset);
// Disk is full
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTDiskFull, SMBStatus.HRDWriteFault, SMBStatus.ErrHrd);
return;
}
catch (DiskOfflineException ex) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILEIO))
m_sess.debugPrintln("Filesystem Offline Error [" + netFile.getFileId() + "] Write File");
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
}
catch (IOException ex) {
// Debug
if (Debug.EnableError && m_sess.hasDebug(SMBSrvSession.Dbg.FILEIO))
m_sess.debugPrintln("File Write Error [" + netFile.getFileId() + "] : " + ex.toString());
// Failed to read the file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.HRDWriteFault, SMBStatus.ErrHrd);
return;
}
// Return the count of bytes actually written
parser.setSuccessStatus();
parser.setParameterCount(6);
parser.setAndXCommand(0xFF);
parser.setParameter(1, 0); // AndX offset
parser.setParameter(2, wrtlen);
parser.setParameter(3, 0xFFFF);
if (dataLenHigh > 0) {
parser.setParameter(4, dataLen >> 16);
parser.setParameter(5, 0);
}
else {
parser.setParameterLong(4, 0);
}
parser.setByteCount(0);
parser.setParameter(1, parser.getLength());
// Send the write response
m_sess.sendResponseSMB(smbPkt);
// Report file size change notifications every so often
//
// We do not report every write due to the increased overhead of change notifications
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (netFile.getWriteCount() % FileSizeChangeRate == 0 && diskCtx.hasFileServerNotifications() && netFile.getFullName() != null) {
// Get the change handler
NotifyChangeHandler changeHandler = diskCtx.getChangeHandler();
// File size changed
changeHandler.notifyFileSizeChanged(netFile.getFullName());
}
}
/**
* Process the file create/open request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTCreateAndX(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid NT create andX request
if (parser.checkPacketIsValid(24, 1) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVInvalidTID, SMBStatus.ErrSrv);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// If the connection is to the IPC$ remote admin named pipe pass the request to the IPC
// handler. If the device is not a disk type device then return an error.
if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
// Use the IPC$ handler to process the request
IPCHandler.processIPCRequest(m_sess, smbPkt);
return;
}
else if (conn.getSharedDevice().getType() != ShareType.DISK) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Extract the NT create andX parameters
NTParameterPacker prms = new NTParameterPacker(parser.getBuffer(), SMBV1.PARAMWORDS + 5);
int nameLen = prms.unpackWord();
int flags = prms.unpackInt();
int rootFID = prms.unpackInt();
int accessMask = prms.unpackInt();
long allocSize = prms.unpackLong();
int attrib = prms.unpackInt();
SharingMode shrAccess = SharingMode.fromInt(prms.unpackInt());
CreateDisposition createDisp = CreateDisposition.fromInt(prms.unpackInt());
int createOptn = prms.unpackInt();
ImpersonationLevel impersonLev = ImpersonationLevel.fromInt(prms.unpackInt());
int secFlags = prms.unpackByte();
// Extract the filename string
String fileName = DataPacker.getUnicodeString(parser.getBuffer(), DataPacker.wordAlign(parser.getByteOffset()),
nameLen / 2);
if (fileName == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Access the disk interface that is associated with the shared device
DiskInterface disk = null;
try {
// Get the disk interface for the share
disk = (DiskInterface) conn.getSharedDevice().getInterface();
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Check if the file name contains a file stream name. If the disk interface does not
// implement the optional NTFS streams interface then return an error status, not supported.
if (fileName.indexOf(FileOpenParams.StreamSeparator) != -1) {
// Check if the driver implements the NTFS streams interface and it is enabled
boolean streams = false;
if (disk instanceof NTFSStreamsInterface) {
// Check if streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
streams = ntfsStreams.hasStreamsEnabled(m_sess, conn);
}
// Check if streams are enabled/available
if (streams == false) {
// Return a file not found error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
}
// Create the file open parameters to be passed to the disk interface
FileOpenParams params = new FileOpenParams(fileName, createDisp, accessMask, attrib, shrAccess, allocSize, createOptn,
rootFID, impersonLev, secFlags, parser.getProcessIdFull());
// Set the create flags, with oplock requests
params.setNTCreateFlags(flags);
params.setTreeId(treeId);
params.setSession(m_sess);
// If an oplock was requested then set an oplock owner
SMBV1OplockOwner oplockOwner = null;
if ( params.requestedOplockType() != OpLockType.LEVEL_NONE) {
oplockOwner = new SMBV1OplockOwner( treeId, parser.getProcessId(), parser.getUserId());
params.setOplockOwner( oplockOwner);
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("NT Create AndX [" + treeId + "] params=" + params);
// Check if the file name is valid
if (FileName.isValidPath(params.getPath()) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Access the disk interface and open the requested file
int fid;
NetworkFile netFile = null;
int respAction = 0;
OpLockDetails oplock = null;
try {
// Check if the requested file already exists
FileStatus fileSts = disk.fileExists(m_sess, conn, params.getFullPath());
// Check if the path is to a folder, make sure the Directory flag is set in the open parameters for oplock checking
if (params.isDirectory() == false && fileSts == FileStatus.DirectoryExists) {
params.setCreateOption(WinNT.CreateDirectory);
}
// Check if the file exists and it is a pseudo file, in which case the file already exists so change a create request to
// an open request
if (fileSts == FileStatus.FileExists) {
// Check for a pseudo file
FileInfo finfo = disk.getFileInformation(m_sess, conn, params.getFullPath());
if (finfo != null && finfo.isPseudoFile()) {
createDisp = CreateDisposition.OPEN;
// Clear any oplock request for pseudo files
if (params.requestBatchOpLock() || params.requestExclusiveOpLock()) {
if (params.requestExtendedResponse())
params.setNTCreateFlags(WinNT.ExtendedResponse);
else
params.setNTCreateFlags(0);
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln("Converted create to open for pseudo file " + params);
}
}
// Check if the file should be created
if (fileSts == FileStatus.NotExist) {
// Check if the file should be created if it does not exist
if (createDisp == CreateDisposition.CREATE || createDisp == CreateDisposition.OPEN_IF
|| createDisp == CreateDisposition.OVERWRITE_IF || createDisp == CreateDisposition.SUPERSEDE) {
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Check if a new file or directory should be created
if ((createOptn & WinNT.CreateDirectory) == 0) {
// Create a new file
netFile = disk.createFile(m_sess, conn, params);
// Indicate the file was created
if (netFile != null && m_sess.hasDebug(SMBSrvSession.Dbg.BENCHMARK)) {
netFile.setStatusFlag(NetworkFile.Flags.CREATED, true);
netFile.setCreationDate(System.currentTimeMillis());
}
// Check if an oplock was requested, grant the oplock if possible and return the granted oplock details, or null
// if no oplock granted or requested.
oplock = OpLockHelper.grantOpLock(m_sess, smbPkt, disk, conn, params, netFile);
}
else {
// Split the path and walk to see which folder(s) need creating
String[] paths = FileName.splitAllPaths(params.getPath());
StringBuilder pathStr = new StringBuilder(params.getPath().length());
FileStatus fldrSts = FileStatus.Unknown;
int idx = 0;
while (idx < paths.length) {
// Add the current path component and check if it exists, and it is a folder
pathStr.append(FileName.DOS_SEPERATOR_STR);
pathStr.append(paths[idx++]);
fldrSts = disk.fileExists(m_sess, conn, pathStr.toString());
// If the current path exists and it is a file then return an error
if (fldrSts == FileStatus.FileExists) {
if (idx < paths.length)
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameCollision, SMBStatus.DOSFileAlreadyExists, SMBStatus.ErrDos);
else
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.DOSDirectoryInvalid, SMBStatus.ErrDos);
return;
}
else if (fldrSts == FileStatus.NotExist) {
// Create the current part of the path
FileOpenParams fldrParams = new FileOpenParams(pathStr.toString(), createDisp, accessMask, attrib, shrAccess, allocSize, createOptn,
rootFID, impersonLev, secFlags, parser.getProcessIdFull());
disk.createDirectory(m_sess, conn, fldrParams);
}
}
// Open the requested folder, should now exist
netFile = disk.openFile(m_sess, conn, params);
// Indicate the directory was created
if (netFile != null && m_sess.hasDebug(SMBSrvSession.Dbg.BENCHMARK)) {
netFile.setStatusFlag(NetworkFile.Flags.CREATED, true);
netFile.setCreationDate(System.currentTimeMillis());
}
}
// Check if the delete on close option is set
if (netFile != null && (createOptn & WinNT.CreateDeleteOnClose) != 0)
netFile.setDeleteOnClose(true);
// Indicate that the file did not exist and was created
respAction = FileAction.FileCreated;
}
else {
// Check if the path is a directory
if (fileSts == FileStatus.DirectoryExists) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameCollision, SMBStatus.DOSFileAlreadyExists,
SMBStatus.ErrDos);
return;
}
else {
// Return a file not found error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
}
}
else if (createDisp == CreateDisposition.CREATE) {
// Check for a file or directory
if (fileSts == FileStatus.FileExists || fileSts == FileStatus.DirectoryExists) {
// Return a file exists error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameCollision, SMBStatus.DOSFileAlreadyExists, SMBStatus.ErrDos);
return;
}
else {
// Return an access denied exception
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
}
else {
// Check if the open should be a file, not a directory
if ((createOptn & WinNT.CreateNonDirectory) != 0 && fileSts == FileStatus.DirectoryExists) {
// Return a file is a directory error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTFileIsADirectory, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Check if the filesystem supports oplocks, check if there is an oplock on the file
OpLockHelper.checkOpLock(m_sess, smbPkt, disk, params, conn);
// Open the requested file/directory
netFile = disk.openFile(m_sess, conn, params);
// Check if an oplock was requested, grant the oplock if possible and return the granted oplock details, or null
// if no oplock granted or requested.
oplock = OpLockHelper.grantOpLock(m_sess, smbPkt, disk, conn, params, netFile);
// Check if the file should be truncated
if (createDisp == CreateDisposition.SUPERSEDE || createDisp == CreateDisposition.OVERWRITE_IF) {
// Truncate the file
disk.truncateFile(m_sess, conn, netFile, 0L);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln(" [" + treeId + "] name=" + fileName + " truncated");
// Treat the file as if it is a newly created file
if (netFile != null && m_sess.hasDebug(SMBSrvSession.Dbg.BENCHMARK)) {
netFile.setStatusFlag(NetworkFile.Flags.CREATED, true);
netFile.setCreationDate(System.currentTimeMillis());
}
}
// Set the file action response
respAction = FileAction.FileExisted;
}
// Add the file to the list of open files for this tree connection
fid = conn.addFile(netFile, getSession());
// If the file has been granted an oplock then update the file id, needed for the oplock break
if (oplock != null && (oplock.getLockType() != OpLockType.LEVEL_NONE && oplock.getLockType() != OpLockType.LEVEL_II)) {
oplock.setOwnerFileId( fid);
oplockOwner.setFileId( fid);
}
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln(" [" + treeId + "] name=" + fileName + " fid=" + fid + ", fileId=" + netFile.getFileId() + ", opLock=" + oplock);
}
catch (TooManyFilesException ex) {
// Too many files are open on this connection, cannot open any more files.
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTTooManyOpenFiles, SMBStatus.DOSTooManyOpenFiles, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (FileExistsException ex) {
// File/directory already exists
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameCollision, SMBStatus.DOSFileAlreadyExists, SMBStatus.ErrDos);
return;
}
catch (FileSharingException ex) {
// Return a sharing violation error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTSharingViolation, SMBStatus.DOSFileSharingConflict, SMBStatus.ErrDos);
return;
}
catch (FileOfflineException ex) {
// File data is unavailable
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTFileOffline, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (FileNameException ex) {
// File name too long or contains invalid characters
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameInvalid, SMBStatus.DOSInvalidFormat, SMBStatus.ErrDos);
return;
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (DiskFullException ex) {
// Disk is full
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTDiskFull, SMBStatus.HRDWriteFault, SMBStatus.ErrHrd);
return;
}
catch (DeferredPacketException ex) {
// Deferred packet, oplock break in progress, rethrow the exception
throw ex;
}
catch (IOException ex) {
// Failed to open the file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
// Build the NT create andX response
boolean extendedResponse = params.requestExtendedResponse();
parser.setParameterCount(extendedResponse ? 42 : 34);
parser.setAndXCommand(0xFF);
parser.setParameter(1, 0); // AndX offset
prms.reset(parser.getBuffer(), SMBV1.PARAMWORDS + 4);
// Pack the oplock type, if granted
if (oplock != null)
prms.packByte(oplock.getLockType().intValue());
else
prms.packByte(0);
// Pack the file id
prms.packWord(fid);
prms.packInt(respAction);
// Pack the file/directory dates
//
// Creation
// Access
// Modify
// Change
if (netFile.hasCreationDate())
prms.packLong(NTTime.toNTTime(netFile.getCreationDate()));
else
prms.packLong(0);
if (netFile.hasAccessDate())
prms.packLong(NTTime.toNTTime(netFile.getAccessDate()));
else {
// Use the modify date/time if access ate/time has not been set
if (netFile.hasModifyDate())
prms.packLong(NTTime.toNTTime(netFile.getModifyDate()));
else
prms.packLong(0);
}
if (netFile.hasModifyDate()) {
long modDate = NTTime.toNTTime(netFile.getModifyDate());
prms.packLong(modDate);
prms.packLong(modDate);
}
else {
prms.packLong(0); // Last write time
prms.packLong(0); // Change time
}
prms.packInt(netFile.getFileAttributes());
// Pack the file size/allocation size
long fileSize = netFile.getFileSize();
if (fileSize > 0L)
fileSize = (fileSize + 512L) & 0xFFFFFFFFFFFFFE00L;
prms.packLong(fileSize); // Allocation size
prms.packLong(netFile.getFileSize()); // End of file
prms.packWord(0); // File type - disk file
prms.packWord(extendedResponse ? 7 : 0); // Device state
prms.packByte(netFile.isDirectory() ? 1 : 0);
prms.packWord(0); // byte count = 0
// Pack the extra extended response area, if requested
if (extendedResponse == true) {
// 22 byte block of zeroes
prms.packLong(0);
prms.packLong(0);
prms.packInt(0);
prms.packWord(0);
// Pack the permissions
if (netFile.isDirectory() || netFile.getAllowedAccess() == NetworkFile.Access.READ_WRITE)
prms.packInt(AccessMode.NTFileGenericAll);
else
prms.packInt(AccessMode.NTFileGenericRead);
// 8 byte block of zeroes
prms.packInt(0);
prms.packInt(0);
}
// Set the AndX offset
int endPos = prms.getPosition();
parser.setParameter(1, endPos - RFCNetBIOSProtocol.HEADER_LEN);
// Set the status
parser.setLongErrorCode(SMBStatus.NTSuccess);
// Check if there is a chained request
if (parser.hasAndXCommand()) {
// Process the chained requests
endPos = procAndXCommands(smbPkt, parser, netFile);
}
// Send the response packet
m_sess.sendResponseSMB(smbPkt, endPos - RFCNetBIOSProtocol.HEADER_LEN);
// Check if there are any file/directory change notify requests active
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (diskCtx.hasFileServerNotifications() && respAction == FileAction.FileCreated) {
// Check if a file or directory has been created
if (netFile.isDirectory())
diskCtx.getChangeHandler().notifyDirectoryChanged(NotifyAction.Added, fileName);
else
diskCtx.getChangeHandler().notifyFileChanged(NotifyAction.Added, fileName);
}
}
/**
* Process the cancel request.
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTCancel(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that the received packet looks like a valid NT cancel request
if (parser.checkPacketIsValid(0, 0) == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Find the matching notify request and remove it
NotifyRequest req = m_sess.findNotifyRequest(parser.getMultiplexId(), parser.getTreeId(), parser.getUserId(),
parser.getProcessId());
if (req != null) {
// Remove the request
m_sess.removeNotifyRequest(req);
// Return a cancelled status
parser.setParameterCount(0);
parser.setByteCount(0);
// Enable the long error status flag
if (parser.isLongErrorCode() == false)
parser.setFlags2(parser.getFlags2() + SMBV1.FLG2_LONGERRORCODE);
// Set the NT status code
parser.setLongErrorCode(SMBStatus.NTCancelled);
// Set the Unicode strings flag
if (parser.isUnicode() == false)
parser.setFlags2(parser.getFlags2() + SMBV1.FLG2_UNICODE);
// Return the error response to the client
m_sess.sendResponseSMB(smbPkt);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NOTIFY)) {
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
m_sess.debugPrintln("NT Cancel notify mid=" + req.getId() + ", dir=" + req.getWatchPath() + ", queue="
+ diskCtx.getChangeHandler().getRequestQueueSize());
}
}
else {
// Nothing to cancel
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
}
}
/**
* Process an NT transaction
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTTransaction(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that we received enough parameters for a transact2 request
if (parser.checkPacketIsValid(19, 0) == false) {
// Not enough parameters for a valid transact2 request
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree id from the received packet and validate that it is a valid connection id.
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Check if the transaction request is for the IPC$ pipe
if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
IPCHandler.processIPCRequest(m_sess, smbPkt);
return;
}
// Get the NT transaction sub-command code
int subCmd = parser.getNTFunction();
// Check for a notfy change request, this needs special processing
if (subCmd == PacketTypeV1.NTTransNotifyChange) {
// Handle the notify change setup request
procNTTransactNotifyChange(smbPkt, parser);
return;
}
// Create a transact buffer to hold the transaction parameter block and data block
SrvTransactBuffer transBuf = null;
if (parser.getTotalParameterCount() == parser.getParameterBlockCount()
&& parser.getTotalDataCount() == parser.getDataBlockCount()) {
// Create a transact buffer using the packet buffer, the entire request is contained in a single packet
transBuf = new SrvTransactBuffer(parser);
}
else {
// Create a transact buffer to hold the multiple transact request parameter/data blocks
transBuf = new SrvTransactBuffer(parser.getSetupCount(), parser.getTotalParameterCount(), parser.getTotalDataCount());
transBuf.setType(parser.getCommand());
transBuf.setFunction(subCmd);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT Transaction [" + treeId + "] transbuf=" + transBuf);
// Append the setup, parameter and data blocks to the transaction data
byte[] buf = parser.getBuffer();
int cnt = parser.getSetupCount();
if (cnt > 0)
transBuf.appendSetup(buf, parser.getSetupOffset(), cnt * 2);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT Transaction [" + treeId + "] pcnt=" + parser.getNTParameter(4) + ", offset="
+ parser.getNTParameter(5));
cnt = parser.getParameterBlockCount();
if (cnt > 0)
transBuf.appendParameter(buf, parser.getParameterBlockOffset(), cnt);
cnt = parser.getDataBlockCount();
if (cnt > 0)
transBuf.appendData(buf, parser.getDataBlockOffset(), cnt);
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT Transaction [" + treeId + "] cmd=0x" + Integer.toHexString(subCmd) + ", multiPkt="
+ transBuf.isMultiPacket());
// Check for a multi-packet transaction, for a multi-packet transaction we just acknowledge the receive with an
// empty response SMB
if (transBuf.isMultiPacket()) {
// Save the partial transaction data
vc.setTransaction(transBuf);
// Send an intermediate acknowedgement response
m_sess.sendSuccessResponseSMB(smbPkt);
return;
}
// Process the transaction buffer
processNTTransactionBuffer(transBuf, smbPkt, parser);
}
/**
* Process an NT transaction secondary packet
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTTransactionSecondary(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Check that we received enough parameters for a transact2 request
if (parser.checkPacketIsValid(18, 0) == false) {
// Not enough parameters for a valid transact2 request
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree id from the received packet and validate that it is a valid connection id.
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Check if the transaction request is for the IPC$ pipe
if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
IPCHandler.processIPCRequest(m_sess, smbPkt);
return;
}
// Check if there is an active transaction, and it is an NT transaction
if (vc.hasTransaction() == false || vc.getTransaction().isType() != PacketTypeV1.NTTransact) {
// No NT transaction to continue, return an error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the existing transaction buffer
byte[] buf = parser.getBuffer();
SrvTransactBuffer transBuf = vc.getTransaction();
// Append the parameter data to the transaction buffer, if any
int plen = parser.getParameterBlockCount();
if (plen > 0) {
// Append the data to the parameter buffer
DataBuffer paramBuf = transBuf.getParameterBuffer();
paramBuf.appendData(buf, parser.getParameterBlockOffset(), plen);
}
// Append the data block to the transaction buffer, if any
int dlen = parser.getDataBlockCount();
if (dlen > 0) {
// Append the data to the data buffer
DataBuffer dataBuf = transBuf.getDataBuffer();
dataBuf.appendData(buf, parser.getDataBlockOffset(), dlen);
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT Transaction Secondary [" + treeId + "] paramLen=" + plen + ", dataLen=" + dlen);
// Check if the transaction has been received or there are more sections to be received
int totParam = parser.getTotalParameterCount();
int totData = parser.getTotalDataCount();
int paramDisp = parser.getParameterBlockDisplacement();
int dataDisp = parser.getDataBlockDisplacement();
if ((paramDisp + plen) == totParam && (dataDisp + dlen) == totData) {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT Transaction complete, processing ...");
// Clear the in progress transaction
vc.setTransaction(null);
// Process the transaction
processNTTransactionBuffer(transBuf, smbPkt, parser);
}
// No response is sent for a transaction secondary
}
/**
* Process an NT transaction buffer
*
* @param tbuf TransactBuffer
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
private final void processNTTransactionBuffer(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Process the NT transaction buffer
switch (tbuf.getFunction()) {
// Create file/directory
case PacketTypeV1.NTTransCreate:
procNTTransactCreate(tbuf, smbPkt, parser);
break;
// I/O control
case PacketTypeV1.NTTransIOCtl:
procNTTransactIOCtl(tbuf, smbPkt, parser);
break;
// Query security descriptor
case PacketTypeV1.NTTransQuerySecurityDesc:
procNTTransactQuerySecurityDesc(tbuf, smbPkt, parser);
break;
// Set security descriptor
case PacketTypeV1.NTTransSetSecurityDesc:
procNTTransactSetSecurityDesc(tbuf, smbPkt, parser);
break;
// Rename file/directory via handle
case PacketTypeV1.NTTransRename:
procNTTransactRename(tbuf, smbPkt, parser);
break;
// Get user quota
case PacketTypeV1.NTTransGetUserQuota:
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT GetUserQuota transaction");
// Return a not implemented error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNotImplemented, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
break;
// Set user quota
case PacketTypeV1.NTTransSetUserQuota:
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT SetUserQuota transaction");
// Return a not implemented error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNotImplemented, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
break;
// Unknown NT transaction command
default:
// Return an unrecognized command error
if (Debug.EnableError)
m_sess.debugPrintln("NT Error unknown NT transact command = 0x" + Integer.toHexString(tbuf.isType()));
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
break;
}
}
/**
* Process an NT create file/directory transaction
*
* @param tbuf TransactBuffer
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTTransactCreate(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT TransactCreate");
// Check that the received packet looks like a valid NT create transaction
if (tbuf.hasParameterBuffer() && tbuf.getParameterBuffer().getLength() < 52) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = tbuf.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// If the connection is not a disk share then return an error.
if (conn.getSharedDevice().getType() != ShareType.DISK) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Extract the file create parameters
DataBuffer tparams = tbuf.getParameterBuffer();
int flags = tparams.getInt();
int rootFID = tparams.getInt();
int accessMask = tparams.getInt();
long allocSize = tparams.getLong();
int attrib = tparams.getInt();
SharingMode shrAccess = SharingMode.fromInt(tparams.getInt());
CreateDisposition createDisp = CreateDisposition.fromInt(tparams.getInt());
int createOptn = tparams.getInt();
int sdLen = tparams.getInt();
int eaLen = tparams.getInt();
int nameLen = tparams.getInt();
ImpersonationLevel impersonLev = ImpersonationLevel.fromInt(tparams.getInt());
int secFlags = tparams.getByte();
// Extract the filename string
tparams.wordAlign();
String fileName = tparams.getString(nameLen, true);
if (fileName == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Access the disk interface that is associated with the shared device
DiskInterface disk = null;
try {
// Get the disk interface for the share
disk = (DiskInterface) conn.getSharedDevice().getInterface();
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Check if the file name contains a file stream name. If the disk interface does not
// implement the optional NTFS streams interface then return an error status, not supported.
if (fileName.indexOf(FileOpenParams.StreamSeparator) != -1) {
// Check if the driver implements the NTFS streams interface and it is enabled
boolean streams = false;
if (disk instanceof NTFSStreamsInterface) {
// Check if streams are enabled
NTFSStreamsInterface ntfsStreams = (NTFSStreamsInterface) disk;
streams = ntfsStreams.hasStreamsEnabled(m_sess, conn);
}
// Check if streams are enabled/available
if (streams == false) {
// Return a file not found error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
}
// Create the file open parameters to be passed to the disk interface
FileOpenParams params = new FileOpenParams(fileName, createDisp, accessMask, attrib, shrAccess, allocSize, createOptn,
rootFID, impersonLev, secFlags, parser.getProcessIdFull());
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE)) {
m_sess.debugPrintln("NT TransactCreate [" + treeId + "] params=" + params);
m_sess.debugPrintln(" secDescLen=" + sdLen + ", extAttribLen=" + eaLen);
}
// Access the disk interface and open/create the requested file
int fid;
NetworkFile netFile = null;
int respAction = 0;
try {
// Check if the requested file already exists
FileStatus fileSts = disk.fileExists(m_sess, conn, fileName);
if (fileSts == FileStatus.NotExist) {
// Check if the file should be created if it does not exist
if (createDisp == CreateDisposition.CREATE || createDisp == CreateDisposition.OPEN_IF
|| createDisp == CreateDisposition.OVERWRITE_IF || createDisp == CreateDisposition.SUPERSEDE) {
// Check if a new file or directory should be created
if ((createOptn & WinNT.CreateDirectory) == 0) {
// Create a new file
netFile = disk.createFile(m_sess, conn, params);
}
else {
// Create a new directory and open it
disk.createDirectory(m_sess, conn, params);
netFile = disk.openFile(m_sess, conn, params);
}
// Indicate that the file did not exist and was created
respAction = FileAction.FileCreated;
}
else {
// Check if the path is a directory
if (fileSts == FileStatus.DirectoryExists) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameCollision, SMBStatus.DOSFileAlreadyExists,
SMBStatus.ErrDos);
return;
}
else {
// Return a file not found error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
}
}
else if (createDisp == CreateDisposition.CREATE) {
// Check for a file or directory
if (fileSts == FileStatus.FileExists || fileSts == FileStatus.DirectoryExists) {
// Return a file exists error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameCollision, SMBStatus.DOSFileAlreadyExists, SMBStatus.ErrDos);
return;
}
else {
// Return an access denied exception
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
}
else {
// Open the requested file/directory
netFile = disk.openFile(m_sess, conn, params);
// Check if the file should be truncated
if (createDisp == CreateDisposition.SUPERSEDE || createDisp == CreateDisposition.OVERWRITE_IF) {
// Truncate the file
disk.truncateFile(m_sess, conn, netFile, 0L);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.FILE))
m_sess.debugPrintln(" [" + treeId + "] name=" + fileName + " truncated");
}
// Set the file action response
respAction = FileAction.FileExisted;
}
// Add the file to the list of open files for this tree connection
fid = conn.addFile(netFile, getSession());
}
catch (TooManyFilesException ex) {
// Too many files are open on this connection, cannot open any more files.
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTTooManyOpenFiles, SMBStatus.DOSTooManyOpenFiles, SMBStatus.ErrDos);
return;
}
catch (AccessDeniedException ex) {
// Return an access denied error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
catch (FileExistsException ex) {
// File/directory already exists
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNameCollision, SMBStatus.DOSFileAlreadyExists, SMBStatus.ErrDos);
return;
}
catch (FileSharingException ex) {
// Return a sharing violation error
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTSharingViolation, SMBStatus.DOSFileSharingConflict, SMBStatus.ErrDos);
return;
}
catch (FileOfflineException ex) {
// File data is unavailable
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTFileOffline, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (DiskOfflineException ex) {
// Filesystem is offline
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectPathNotFound, SMBStatus.HRDDriveNotReady, SMBStatus.ErrHrd);
return;
}
catch (IOException ex) {
// Failed to open the file
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTObjectNotFound, SMBStatus.DOSFileNotFound, SMBStatus.ErrDos);
return;
}
// Build the NT transaction create response
DataBuffer prms = new DataBuffer(128);
// If an oplock was requested indicate it was granted, for now
if ((flags & WinNT.RequestBatchOplock) != 0) {
// Batch oplock granted
prms.putByte(2);
}
else if ((flags & WinNT.RequestExclusiveOplock) != 0) {
// Exclusive oplock granted
prms.putByte(1);
}
else {
// No oplock granted
prms.putByte(0);
}
prms.putByte(0); // alignment
// Pack the file id
prms.putShort(fid);
prms.putInt(respAction);
// EA error offset
prms.putInt(0);
// Pack the file/directory dates
if (netFile.hasCreationDate())
prms.putLong(NTTime.toNTTime(netFile.getCreationDate()));
else
prms.putLong(0);
if (netFile.hasModifyDate()) {
long modDate = NTTime.toNTTime(netFile.getModifyDate());
prms.putLong(modDate);
prms.putLong(modDate);
prms.putLong(modDate);
}
else {
prms.putLong(0); // Last access time
prms.putLong(0); // Last write time
prms.putLong(0); // Change time
}
prms.putInt(netFile.getFileAttributes());
// Pack the file size/allocation size
prms.putLong(netFile.getFileSize()); // Allocation size
prms.putLong(netFile.getFileSize()); // End of file
prms.putShort(0); // File type - disk file
prms.putShort(0); // Device state
prms.putByte(netFile.isDirectory() ? 1 : 0);
// Initialize the transaction response
parser.initTransactReply(prms.getBuffer(), prms.getLength(), null, 0);
// Send back the response
m_sess.sendResponseSMB(smbPkt);
// Check if there are any file/directory change notify requests active
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (diskCtx.hasFileServerNotifications() && respAction == FileAction.FileCreated) {
// Check if a file or directory has been created
if (netFile.isDirectory())
diskCtx.getChangeHandler().notifyDirectoryChanged(NotifyAction.Added, fileName);
else
diskCtx.getChangeHandler().notifyFileChanged(NotifyAction.Added, fileName);
}
}
/**
* Process an NT I/O control transaction
*
* @param tbuf TransactBuffer
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTTransactIOCtl(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = tbuf.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Unpack the request details
DataBuffer setupBuf = tbuf.getSetupBuffer();
int ctrlCode = setupBuf.getInt();
int fid = setupBuf.getShort();
boolean fsctrl = setupBuf.getByte() == 1 ? true : false;
int filter = setupBuf.getByte();
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT IOCtl code=" + NTIOCtl.asString(ctrlCode) + ", fid=" + fid + ", fsctrl=" + fsctrl
+ ", filter=" + filter);
// Access the disk interface that is associated with the shared device
DiskInterface disk = null;
try {
// Get the disk interface for the share
disk = (DiskInterface) conn.getSharedDevice().getInterface();
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Check if the disk interface implements the optional IO control interface
if (disk instanceof IOCtlInterface) {
// Access the IO control interface
IOCtlInterface ioControl = (IOCtlInterface) disk;
SMBSrvPacket respPkt = smbPkt;
try {
// Pass the request to the IO control interface for processing
DataBuffer response = ioControl.processIOControl(m_sess, conn, ctrlCode, fid, tbuf.getDataBuffer(), fsctrl, filter);
// Pack the response
if (response != null) {
// Check if a larger buffer needs to be allocated for the response packet
int respPktLen = parser.calculateResponseLength(0, response.getLength(), 1);
if (parser.getBufferLength() < respPktLen) {
// Allocate a larger response packet
SMBSrvPacket pkt = m_sess.getPacketPool().allocatePacket(respPktLen, smbPkt, parser.getLength());
// Create a new packet from the new buffer
respPkt = new SMBSrvPacket(pkt.getBuffer());
// Create a parser for the response
respPkt.setParser( SMBSrvPacket.Version.V1);
parser = (SMBV1Parser) respPkt.getParser();
}
// Pack the response data block
parser.initTransactReply(null, 0, response.getBuffer(), response.getLength(), 1);
parser.setSetupParameter(0, response.getLength());
}
else {
// Pack an empty response data block
parser.initTransactReply(null, 0, null, 0, 1);
parser.setSetupParameter(0, 0);
}
}
catch (IOControlNotImplementedException ex) {
// Return a not implemented error status
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNotImplemented, SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);
return;
}
catch (SMBException ex) {
// Return the specified SMB status, this should be an NT status code
m_sess.sendErrorResponseSMB(smbPkt, ex.getErrorCode(), SMBStatus.SRVInternalServerError, SMBStatus.ErrSrv);
return;
}
// Send the IOCtl response
m_sess.sendResponseSMB(respPkt);
}
else {
// Send back an error, IOctl not supported
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNotImplemented, SMBStatus.SRVNotSupported, SMBStatus.ErrSrv);
}
}
/**
* Process an NT query security descriptor transaction
*
* @param tbuf TransactBuffer
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTTransactQuerySecurityDesc(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = tbuf.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Unpack the request details
DataBuffer paramBuf = tbuf.getParameterBuffer();
int fid = paramBuf.getShort();
int flags = paramBuf.getShort();
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT QuerySecurityDesc fid=" + fid + ", flags=" + flags);
// Get the file details
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Access the disk interface that is associated with the shared device
DiskInterface disk = null;
try {
// Get the disk interface for the share
disk = (DiskInterface) conn.getSharedDevice().getInterface();
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Check if the disk interface implements the optional security descriptor interface
SMBSrvPacket respPkt = smbPkt;
if (disk instanceof SecurityDescriptorInterface) {
// Access the security descriptor interface
SecurityDescriptorInterface secDescInterface = (SecurityDescriptorInterface) disk;
// Check if this is a buffer length check, if so the maximum returned data count will be zero
if (tbuf.getReturnDataLimit() == 0) {
// Get the security descriptor length
int secDescLen = secDescInterface.getSecurityDescriptorLength(m_sess, conn, netFile);
// Return the security descriptor length in the parameter block
byte[] paramblk = new byte[4];
DataPacker.putIntelInt(secDescLen, paramblk, 0);
// Initialize the transaction reply
parser.initTransactReply(paramblk, paramblk.length, null, 0);
// Set a warning status to indicate the supplied data buffer was too small to return the security descriptor
parser.setLongErrorCode(SMBStatus.NTBufferTooSmall);
}
else {
// Get the security descriptor for the file
SecurityDescriptor secDesc = secDescInterface.loadSecurityDescriptor(m_sess, conn, netFile);
byte[] secBuf = null;
int secLen = 0;
byte[] paramblk = new byte[4];
if (secDesc != null) {
// Pack the security descriptor
DataBuffer buf = new DataBuffer(4096);
try {
secLen = secDesc.saveDescriptor(buf);
secBuf = buf.getBuffer();
}
catch (SaveException ex) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Calculate the available space for the security descriptor in the current packet
parser.initTransactReply(paramblk, paramblk.length, null, 0);
int availLen = respPkt.getBufferLength() - parser.getLength();
if (availLen <= (secLen + 8)) {
// Allocate a larger packet for the response
SMBSrvPacket pkt = m_sess.getPacketPool().allocatePacket(parser.getLength() + secLen + 8, smbPkt);
// Create a new packet from the new buffer
respPkt = new SMBSrvPacket(pkt.getBuffer());
// Create a parser for the response
respPkt.setParser( SMBSrvPacket.Version.V1);
parser = (SMBV1Parser) respPkt.getParser();
}
}
// Return the security descriptor length in the parameter block
DataPacker.putIntelInt(secLen, paramblk, 0);
// Initialize the transaction reply.
parser.initTransactReply(paramblk, paramblk.length, secBuf, secLen);
}
}
else {
// Check if this is a buffer length check, if so the maximum returned data count will be zero
if (tbuf.getReturnDataLimit() == 0) {
// Return the security descriptor length in the parameter block
byte[] paramblk = new byte[4];
DataPacker.putIntelInt(_sdEveryOne.length, paramblk, 0);
// Initialize the transaction reply
parser.initTransactReply(paramblk, paramblk.length, null, 0);
// Set a warning status to indicate the supplied data buffer was too small to return
// the security descriptor
parser.setLongErrorCode(SMBStatus.NTBufferTooSmall);
}
else {
// Return the security descriptor length in the parameter block
byte[] paramblk = new byte[4];
DataPacker.putIntelInt(_sdEveryOne.length, paramblk, 0);
// Initialize the transaction reply. Return the fixed security descriptor that allows anyone to
// access the file/directory
parser.initTransactReply(paramblk, paramblk.length, _sdEveryOne, _sdEveryOne.length);
}
}
// Send back the response
m_sess.sendResponseSMB(respPkt);
}
/**
* Process an NT set security descriptor transaction
*
* @param tbuf TransactBuffer
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTTransactSetSecurityDesc(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Unpack the request details
DataBuffer paramBuf = tbuf.getParameterBuffer();
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = tbuf.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Get the file details
int fid = paramBuf.getShort();
paramBuf.skipBytes(2);
int flags = paramBuf.getInt();
// Unpack the security descriptor
DataBuffer dataBuf = tbuf.getDataBuffer();
SecurityDescriptor secDesc = new SecurityDescriptor();
try {
secDesc.loadDescriptor(dataBuf.getBuffer(), dataBuf.getOffset());
}
catch (LoadException ex) {
// Invalid security descriptor
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN)) {
m_sess.debugPrintln("NT SetSecurityDesc fid=" + fid + ", flags=" + flags);
m_sess.debugPrintln(" sd=" + secDesc);
}
// Access the disk interface that is associated with the shared device
DiskInterface disk = null;
try {
// Get the disk interface for the share
disk = (DiskInterface) conn.getSharedDevice().getInterface();
}
catch (InvalidDeviceInterfaceException ex) {
// Failed to get/initialize the disk interface
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
// Check if the disk interface implements the optional security descriptor interface
if (disk instanceof SecurityDescriptorInterface) {
// Access the security descriptor interface
SecurityDescriptorInterface secDescInterface = (SecurityDescriptorInterface) disk;
// Get the file details
NetworkFile netFile = conn.findFile(fid);
if (netFile == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Save the security descriptor
secDescInterface.saveSecurityDescriptor(m_sess, conn, netFile, secDesc);
// Return a success status
parser.initTransactReply(null, 0, null, 0);
parser.setError(SMBStatus.Success, SMBStatus.Success);
m_sess.sendResponseSMB(smbPkt);
}
else {
// Return a success response
m_sess.sendSuccessResponseSMB(smbPkt);
}
}
/**
* Process an NT change notification transaction
*
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTTransactNotifyChange(SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = parser.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasReadAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Make sure the tree connection is for a disk device
if (conn.getContext() == null || conn.getContext() instanceof DiskDeviceContext == false) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Check if the device has change notification enabled
DiskDeviceContext diskCtx = (DiskDeviceContext) conn.getContext();
if (diskCtx.hasChangeHandler() == false) {
// Return an error status, share does not have change notification enabled
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTNotImplemented, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Unpack the request details
parser.resetSetupPointer();
Set filter = NotifyChange.setFromInt(parser.unpackInt());
int fid = parser.unpackWord();
boolean watchTree = parser.unpackByte() == 1 ? true : false;
int mid = parser.getMultiplexId();
// Get the file details
NetworkFile dir = conn.findFile(fid);
if (dir == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
return;
}
// Get the maximum notifications to buffer whilst waiting for the request to be reset after
// a notification has been triggered
int maxQueue = 0;
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NOTIFY))
m_sess.debugPrintln("NT NotifyChange fid=" + fid + ", mid=" + mid + ", filter=" + filter + ", dir=" + dir.getFullName()
+ ", maxQueue=" + maxQueue);
// Check if there is an existing request in the notify list that matches the new request and
// is in a completed state. If so then the client is resetting the notify request so reuse the existing
// request.
NotifyRequest req = m_sess.findNotifyRequest(dir, filter, watchTree);
if (req != null && req.isCompleted()) {
// Reset the existing request with the new multiplex id
req.setId(mid);
req.setCompleted(false);
// Check if there are any buffered notifications for this session
if (req.hasBufferedEvents() || req.hasNotifyEnum()) {
// Get the buffered events from the request, clear the list from the request
NotifyChangeEventList bufList = req.getBufferedEventList();
req.clearBufferedEvents();
// Send the buffered events
diskCtx.getChangeHandler().sendBufferedNotifications(req, bufList);
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NOTIFY)) {
if (bufList == null)
m_sess.debugPrintln(" Sent buffered notifications, req=" + req.toString() + ", Enum");
else
m_sess.debugPrintln(" Sent buffered notifications, req=" + req.toString() + ", count="
+ bufList.numberOfEvents());
}
}
else {
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NOTIFY))
m_sess.debugPrintln(" Reset notify request, " + req.toString());
}
}
else {
// Create a change notification request
req = new NotifyRequest(filter, watchTree, m_sess, dir, mid, parser.getTreeId(), parser.getProcessId(), parser.getUserId(), maxQueue);
// Add the request to the pending notify change lists
m_sess.addNotifyRequest(req, diskCtx);
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NOTIFY)) {
m_sess.debugPrintln(" Added new request, " + req.toString());
m_sess.debugPrintln(" Global notify mask="
+ diskCtx.getChangeHandler().getGlobalNotifyMask() + ", reqQueue="
+ diskCtx.getChangeHandler().getRequestQueueSize());
}
}
// NOTE: If the change notification request is accepted then no reply is sent to the client.
// A reply will be sent asynchronously if the change notification is triggered.
}
/**
* Process an NT rename via handle transaction
*
* @param tbuf TransactBuffer
* @param smbPkt SMBSrvPacket
* @param parser SMBV1Parser
* @exception IOException I/O error
* @exception SMBSrvException SMB error
*/
protected final void procNTTransactRename(SrvTransactBuffer tbuf, SMBSrvPacket smbPkt, SMBV1Parser parser)
throws IOException, SMBSrvException {
// Get the virtual circuit for the request
VirtualCircuit vc = m_sess.findVirtualCircuit(parser.getUserId());
if (vc == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Get the tree connection details
int treeId = tbuf.getTreeId();
TreeConnection conn = vc.findConnection(treeId);
if (conn == null) {
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos);
return;
}
// Check if the user has the required access permission
if (conn.hasWriteAccess() == false) {
// User does not have the required access rights
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.NTAccessDenied, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
return;
}
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.TRAN))
m_sess.debugPrintln("NT TransactRename");
// Send back an error, NT rename not supported
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
}
/**
* Build a change notification response for the specified change event
*
* @param evt NotifyChangeEvent
* @param req NotifyRequest
* @return SMBSrvPacket
*/
public SMBSrvPacket buildChangeNotificationResponse(NotifyChangeEvent evt, NotifyRequest req) {
// Allocate the NT transaction packet to send the asynchronous notification
SMBSrvPacket smbPkt = new SMBSrvPacket();
// Create a parser for the packet
smbPkt.setParser( SMBSrvPacket.Version.V1);
SMBV1Parser parser = (SMBV1Parser) smbPkt.getParser();
// Build the change notification response SMB
parser.setParameterCount(18);
parser.resetBytePointerAlign();
int pos = parser.getPosition();
parser.setNTParameter(1, 0); // total data count
parser.setNTParameter(3, pos - 4); // offset to parameter block
parser.setCommand(PacketTypeV1.NTTransact);
parser.setLongErrorCode(0);
parser.setFlags(SMBV1.FLG_CANONICAL + SMBV1.FLG_CASELESS);
parser.setFlags2(SMBV1.FLG2_UNICODE + SMBV1.FLG2_LONGERRORCODE);
// Set the response for the current notify request
parser.setMultiplexId(req.getIdAsInt());
parser.setTreeId(req.getTreeId());
parser.setUserId(req.getUserId());
parser.setProcessId(req.getProcessId());
// Check if there are notify events or this is a request to the client to enumerate the folder
if ( req.hasNotifyEnum() == false) {
// Get the path for the event
String relName = evt.getFileName();
if (relName == null)
relName = evt.getShortFileName();
// DEBUG
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.NOTIFY))
m_sess.debugPrintln(" Notify evtPath=" + evt.getFileName() + ", MID=" + req.getId() + ", reqPath=" + req.getWatchPath() + ", relative=" + relName);
// Pack the notification structure
parser.packInt(0); // offset to next structure
parser.packInt(evt.getAction().intValue()); // action
parser.packInt(relName.length() * 2); // file name length
parser.packString(relName, true, false);
// Check if the event is a file/directory rename, if so then add the old file/directory details
if (evt.getAction() == NotifyAction.RenamedNewName &&
evt.hasOldFileName()) {
// Set the offset from the first structure to this structure
int newPos = DataPacker.longwordAlign(parser.getPosition());
DataPacker.putIntelInt(newPos - pos, parser.getBuffer(), pos);
// Get the old file name
relName = FileName.makeRelativePath(req.getWatchPath(), evt.getOldFileName());
if (relName == null)
relName = evt.getOldFileName();
// Add the old file/directory name details
parser.packInt(0); // offset to next structure
parser.packInt(NotifyAction.RenamedOldName.intValue());
parser.packInt(relName.length() * 2); // file name length
parser.packString(relName, true, false);
}
// Calculate the parameter block length, longword align the buffer position
int prmLen = parser.getPosition() - pos;
parser.alignBytePointer();
pos = (pos + 3) & 0xFFFFFFFC;
// Set the parameter block length
parser.setNTParameter(0, prmLen); // total parameter block count
parser.setNTParameter(2, prmLen); // parameter block count for this packet
parser.setNTParameter(6, parser.getPosition() - 4); // data block offset
parser.setByteCount();
}
else {
// Return a status code to indicate that a folder search is required
parser.setLongErrorCode(SMBStatus.NTNotifyEnumDir);
}
// DEBUG
// parser.DumpPacket();
// Return the change notification response packet
return smbPkt;
}
/**
* Build an oplock break asynchronous response, sent from the server to the client
*
* @param oplock LocalOpLockDetails
* @return SMBSrvPacket
*/
public SMBSrvPacket buildOpLockBreakResponse(LocalOpLockDetails oplock) {
// Get the oplock owner details
SMBV1OplockOwner oplockOwner = (SMBV1OplockOwner) oplock.getOplockOwner();
if ( oplockOwner == null)
return null;
// Allocate a packet for the oplock break request to be sent on the owner client session
SMBSrvPacket opBreakPkt = new SMBSrvPacket(128);
// Set the associated parser
opBreakPkt.setParser( SMBSrvPacket.Version.V1);
SMBV1Parser parser = (SMBV1Parser) opBreakPkt.getParser();
// Build the oplock break request
opBreakPkt.clearHeader();
parser.setCommand(PacketTypeV1.LockingAndX);
parser.setFlags(0);
parser.setFlags2(0);
parser.setTreeId(oplockOwner.getTreeId());
parser.setProcessId(0xFFFF);
parser.setUserId(0);
parser.setMultiplexId(0xFFFF);
parser.setParameterCount(8);
parser.setAndXCommand(PacketTypeV1.NoChainedCommand);
parser.setParameter(1, 0); // AndX offset
parser.setParameter(2, oplockOwner.getFileId()); // FID
parser.setParameter(3, LockingAndX.OplockBreak + LockingAndX.Level2OpLock);
parser.setParameterLong(4, 0); // timeout
parser.setParameter(6, 0); // number of unlocks
parser.setParameter(7, 0); // number of locks
parser.setByteCount(0);
// Mark the packet as a request packet
parser.setRequestPacket(true);
// Return the oplock break packet
return opBreakPkt;
}
/**
* Run the NT SMB protocol handler to process the received SMB packet
*
* @param smbPkt SMBSrvPacket
* @return boolean true if the packet was processed, else false
* @exception IOException I/O error
* @exception SMBSrvException SMB error
* @exception TooManyConnectionsException No more connections available
*/
public boolean runProtocol(SMBSrvPacket smbPkt)
throws java.io.IOException, SMBSrvException, TooManyConnectionsException {
// Check if the received packet has a valid SMB v1 signature
if (smbPkt.isSMB1() == false)
throw new IOException("Invalid SMB signature");
// Get the SMB parser
SMBV1Parser parser = (SMBV1Parser) smbPkt.getParser();
if ( parser == null)
throw new IOException( "SMB packet does not have a parser");
// Debug
if (Debug.EnableInfo && m_sess.hasDebug(SMBSrvSession.Dbg.STATE) &&
parser.hasChainedCommand())
m_sess.debugPrintln("AndX Command = 0x" + Integer.toHexString(parser.getAndXCommand()));
// Reset the byte unpack offset
parser.resetBytePointer();
// Set the process id from the received packet, this can change for the same session and
// needs to be set for lock ownership checking
//
// TODO: Need to remove this
m_sess.setProcessId(parser.getProcessId());
// Determine the SMB command type
boolean handledOK = true;
switch (parser.getCommand()) {
// NT Session setup
case PacketTypeV1.SessionSetupAndX:
procSessionSetup(smbPkt, parser);
break;
// Tree connect
case PacketTypeV1.TreeConnectAndX:
procTreeConnectAndX(smbPkt, parser);
break;
// Transaction/transaction2
case PacketTypeV1.Transaction:
case PacketTypeV1.Transaction2:
procTransact2(smbPkt, parser);
break;
// Transaction/transaction2 secondary
case PacketTypeV1.TransactionSecond:
case PacketTypeV1.Transaction2Second:
procTransact2Secondary(smbPkt, parser);
break;
// Close a search started via the FindFirst transaction2 command
case PacketTypeV1.FindClose2:
procFindClose(smbPkt, parser);
break;
// Open a file
case PacketTypeV1.OpenAndX:
procOpenAndX(smbPkt, parser);
break;
// Close a file
case PacketTypeV1.CloseFile:
procCloseFile(smbPkt, parser);
break;
// Read a file
case PacketTypeV1.ReadAndX:
procReadAndX(smbPkt, parser);
break;
// Write to a file
case PacketTypeV1.WriteAndX:
procWriteAndX(smbPkt, parser);
break;
// Rename file
case PacketTypeV1.RenameFile:
procRenameFile(smbPkt, parser);
break;
// Delete file
case PacketTypeV1.DeleteFile:
procDeleteFile(smbPkt, parser);
break;
// Delete directory
case PacketTypeV1.DeleteDirectory:
procDeleteDirectory(smbPkt, parser);
break;
// Tree disconnect
case PacketTypeV1.TreeDisconnect:
procTreeDisconnect(smbPkt, parser);
break;
// Lock/unlock regions of a file
case PacketTypeV1.LockingAndX:
procLockingAndX(smbPkt, parser);
break;
// Logoff a user
case PacketTypeV1.LogoffAndX:
procLogoffAndX(smbPkt, parser);
break;
// NT Create/open file
case PacketTypeV1.NTCreateAndX:
procNTCreateAndX(smbPkt, parser);
break;
// Tree connection (without AndX batching)
case PacketTypeV1.TreeConnect:
super.runProtocol(smbPkt);
break;
// NT cancel
case PacketTypeV1.NTCancel:
procNTCancel(smbPkt, parser);
break;
// NT transaction
case PacketTypeV1.NTTransact:
procNTTransaction(smbPkt, parser);
break;
// NT transaction secondary
case PacketTypeV1.NTTransactSecond:
procNTTransactionSecondary(smbPkt, parser);
break;
// Echo request
case PacketTypeV1.Echo:
super.procEcho(smbPkt, parser);
break;
// Default
default:
// Get the tree connection details, if it is a disk or printer type connection then pass the request to
// the core protocol handler
int treeId = parser.getTreeId();
TreeConnection conn = null;
if (treeId != -1)
conn = m_sess.findTreeConnection(smbPkt);
if (conn != null) {
// Check if this is a disk or print connection, if so then send the request to the core protocol handler
if (conn.getSharedDevice().getType() == ShareType.DISK || conn.getSharedDevice().getType() == ShareType.PRINTER) {
// Chain to the core protocol handler
handledOK = super.runProtocol(smbPkt);
}
else if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) {
// Send the request to IPC$ remote admin handler
IPCHandler.processIPCRequest(m_sess, smbPkt);
handledOK = true;
}
}
else {
// Need to send a response or the client may hang
m_sess.sendErrorResponseSMB(smbPkt, SMBStatus.SRVInvalidTID, SMBStatus.ErrSrv);
}
break;
}
// Run any request post processors
runRequestPostProcessors(m_sess);
// Return the handled status
return handledOK;
}
}