From 488450a9880b006ac26234c44956b90e68f85e63 Mon Sep 17 00:00:00 2001 From: Derek Hulley Date: Tue, 19 Dec 2006 14:28:55 +0000 Subject: [PATCH] Merged 1.4 to HEAD svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@4313 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@4314 . svn merge svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@4317 svn://svn.alfresco.com:3691/alfresco/BRANCHES/V1.4@4318 . git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4656 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- config/alfresco/desktop/Alfresco.exe | Bin 393216 -> 393216 bytes source/cpp/CAlfrescoApp/CAlfrescoApp.cpp | 85 ++- source/cpp/CAlfrescoApp/CAlfrescoApp.rc | 5 + .../includes/alfresco/Alfresco.hpp | 4 + .../CAlfrescoApp/source/alfresco/Alfresco.cpp | 143 ++-- .../server/auth/CifsAuthenticator.java | 107 +-- .../filesys/server/auth/ClientInfo.java | 24 + .../auth/EnterpriseCifsAuthenticator.java | 140 ++-- .../smb/server/CoreProtocolHandler.java | 157 ++--- .../filesys/smb/server/DCERPCHandler.java | 15 +- .../filesys/smb/server/IPCHandler.java | 30 +- .../smb/server/LanManProtocolHandler.java | 187 ++++-- .../filesys/smb/server/NTProtocolHandler.java | 421 ++++++++---- .../filesys/smb/server/SMBSrvException.java | 12 +- .../filesys/smb/server/SMBSrvSession.java | 608 ++++++------------ .../filesys/smb/server/VirtualCircuit.java | 525 +++++++++++++++ .../smb/server/VirtualCircuitList.java | 221 +++++++ 17 files changed, 1807 insertions(+), 877 deletions(-) create mode 100644 source/java/org/alfresco/filesys/smb/server/VirtualCircuit.java create mode 100644 source/java/org/alfresco/filesys/smb/server/VirtualCircuitList.java diff --git a/config/alfresco/desktop/Alfresco.exe b/config/alfresco/desktop/Alfresco.exe index df18004bab7527d9bae7064f8b24f5bb2f3fb7ba..4025d6490ab38d9d1e5deffc5a434eaccc34f174 100644 GIT binary patch delta 96814 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%0A|>X{9&gOnI6ojc=01#N}!$zvYa4As_U&w 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

_qUl_0mkFD|U!UhM45)-lEzdsvV-bu&2tm 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 z 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 diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp b/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp index 276faa7484..6cbdc2f2d9 100644 --- a/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.cpp @@ -42,7 +42,6 @@ BEGIN_MESSAGE_MAP(CAlfrescoApp, CWinApp) ON_COMMAND(ID_HELP, CWinApp::OnHelp) END_MESSAGE_MAP() - // CCAlfrescoApp construction CAlfrescoApp::CAlfrescoApp() @@ -102,9 +101,80 @@ BOOL CAlfrescoApp::InitInstance() String folderPath = appPath.substring(0, pos); String exeName = appPath.substring(pos + 1); + // Create a list of the command line arguments + + StringList argList; + bool argSetWorkDir = false; + + for ( int i = 1; i < __argc; i++) { + + // Check if the argument is a path or switch + + String arg = __wargv[i]; + + if ( arg.startsWith( "/")) { + + // Check for the set working directory switch + + if ( arg.equalsIgnoreCase( "/D")) { + argSetWorkDir = true; + + // DEBUG + + DBGOUT_TS << "/D switch specified" << endl; + } + else { + String msg = L"Invalid command line switch - "; + msg.append( arg); + AfxMessageBox( msg.data(), MB_OK | MB_ICONSTOP); + DBGOUT_TS << "Error, " << msg << endl; + return 2; + } + } + else { + + // Add the path to the argument list + + argList.addString( arg); + } + } + + // Check if the working directory should be set to the path of the first document + + if ( argSetWorkDir == true) { + + // Check if there are any document paths + + if ( argList.numberOfStrings() == 0) { + AfxMessageBox( L"Cannot set working directory, no document paths", MB_OK | MB_ICONSTOP); + DBGOUT_TS << "Error, cannot set working directory, no document paths" << endl; + return 3; + } + + // Get the first document path and remove the file name + + String docPath = argList[0]; + pos = docPath.lastIndexOf( PathSeperator); + + if ( pos < 0) { + AfxMessageBox( L"Invalid document path", MB_OK | MB_ICONSTOP); + DBGOUT_TS << "Error, invalid document path, " << docPath << endl; + return 4; + } + + // Set the document path as the working directory folder + + folderPath = docPath.substring(0, pos); + + // DEBUG + + DBGOUT_TS << "Using document path as working directory, " << folderPath << endl; + } + // Create the Alfresco interface AlfrescoInterface alfresco(folderPath); + if ( alfresco.isAlfrescoFolder()) { try { @@ -158,16 +228,9 @@ BOOL CAlfrescoApp::InitInstance() if ( actionInfo.hasAttribute(AttrMultiplePaths)) { - // Build a list of paths from the command line arguments - - StringList pathList; - - for ( int i = 1; i < __argc; i++) - pathList.addString( String(__wargv[i])); - // Run the action - runAction( alfresco, pathList, actionInfo); + runAction( alfresco, argList, actionInfo); } // Check if the action supports file/folder targets @@ -176,12 +239,12 @@ BOOL CAlfrescoApp::InitInstance() // Pass one path at a time to the action - for ( int i = 1; i < __argc; i++) { + for ( size_t i = 0; i < argList.numberOfStrings(); i++) { // Create a path list with a single path StringList pathList; - pathList.addString( String(__wargv[i])); + pathList.addString( argList[i]); // Run the action diff --git a/source/cpp/CAlfrescoApp/CAlfrescoApp.rc b/source/cpp/CAlfrescoApp/CAlfrescoApp.rc index ce5908d0ca..e716621ed5 100644 --- a/source/cpp/CAlfrescoApp/CAlfrescoApp.rc +++ b/source/cpp/CAlfrescoApp/CAlfrescoApp.rc @@ -132,6 +132,11 @@ BEGIN IDS_ABOUTBOX "&About CAlfrescoApp..." END +STRINGTABLE +BEGIN + AFX_IDS_APP_TITLE "Alfresco Desktop Action" +END + #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp b/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp index 397bd18831..46366d0c6c 100644 --- a/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp +++ b/source/cpp/CAlfrescoApp/includes/alfresco/Alfresco.hpp @@ -169,6 +169,10 @@ public: DesktopResponse runAction(AlfrescoActionInfo& action, DesktopParams& params); + // Set the root path to be used as the working directory + + bool setRootPath( const wchar_t* rootPath); + private: // Send an I/O control request, receive and validate the response diff --git a/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp b/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp index b35d4e6922..35f5ba77ec 100644 --- a/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp +++ b/source/cpp/CAlfrescoApp/source/alfresco/Alfresco.cpp @@ -44,67 +44,9 @@ AlfrescoInterface::AlfrescoInterface(String& path) { m_protocolVersion = 1; - // Check if the path is to a mapped drive + // Set the working directory path - String alfPath = path; - - if ( alfPath.length() >= 3 && alfPath.substring(1,3).equals( L":\\")) { - - // Try and convert the local path to a UNC path - - m_mappedDrive = alfPath.substring(0, 2); - wchar_t remPath[MAX_PATH]; - DWORD remPathLen = MAX_PATH; - - DWORD sts = WNetGetConnection(( LPWSTR) m_mappedDrive.data(), remPath, &remPathLen); - if ( sts != NO_ERROR) - return; - - // Build the UNC path to the folder - - alfPath = remPath; - if ( alfPath.endsWith( PathSeperator) == false) - alfPath.append( PathSeperator); - - if ( path.length() > 3) - alfPath.append( path.substring( 3)); - } - - // Save the UNC path - - m_uncPath = alfPath; - - // Check if the UNC path is valid - - if ( m_uncPath.startsWith(UNCPathPrefix)) { - - // Strip any trailing separator from the path - - if ( m_uncPath.endsWith(PathSeperator)) - m_uncPath = m_uncPath.substring(0, m_uncPath.length() - 1); - - // Make sure the path is to a folder - - DWORD attr = GetFileAttributes(m_uncPath); - - if ( attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) { - - // Open the path - - m_handle = CreateFile(m_uncPath, FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); - } - - // Set the root path - - int pos = m_uncPath.indexOf( PathSeperator, 2); - if ( pos != -1) { - pos = m_uncPath.indexOf( PathSeperator, pos + 1); - if ( pos == -1) - m_rootPath = m_uncPath; - else - m_rootPath = m_uncPath.substring(0, pos); - } - } + setRootPath( path); } /** @@ -374,6 +316,87 @@ void AlfrescoInterface::sendIOControl( const unsigned int ctlCode, DataBuffer& r throw Exception( L"Send I/O control error", Integer::toString( GetLastError())); } +/** + * Set the root path to be used as the working directory + * + * @param rootPath const wchar_t* + * @return bool + */ +bool AlfrescoInterface::setRootPath( const wchar_t* rootPath) { + + // Close the existing folder, if valid + + if ( m_handle != INVALID_HANDLE_VALUE) + CloseHandle(m_handle); + + // Check if the path is to a mapped drive + + String path = rootPath; + String alfPath = rootPath; + + if ( alfPath.length() >= 3 && alfPath.substring(1,3).equals( L":\\")) { + + // Try and convert the local path to a UNC path + + m_mappedDrive = alfPath.substring(0, 2); + wchar_t remPath[MAX_PATH]; + DWORD remPathLen = MAX_PATH; + + DWORD sts = WNetGetConnection(( LPWSTR) m_mappedDrive.data(), remPath, &remPathLen); + if ( sts != NO_ERROR) + return false; + + // Build the UNC path to the folder + + alfPath = remPath; + if ( alfPath.endsWith( PathSeperator) == false) + alfPath.append( PathSeperator); + + if ( path.length() > 3) + alfPath.append( path.substring( 3)); + } + + // Save the UNC path + + m_uncPath = alfPath; + + // Check if the UNC path is valid + + if ( m_uncPath.startsWith(UNCPathPrefix)) { + + // Strip any trailing separator from the path + + if ( m_uncPath.endsWith(PathSeperator)) + m_uncPath = m_uncPath.substring(0, m_uncPath.length() - 1); + + // Make sure the path is to a folder + + DWORD attr = GetFileAttributes(m_uncPath); + + if ( attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) { + + // Open the path + + m_handle = CreateFile(m_uncPath, FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + } + + // Set the root path + + int pos = m_uncPath.indexOf( PathSeperator, 2); + if ( pos != -1) { + pos = m_uncPath.indexOf( PathSeperator, pos + 1); + if ( pos == -1) + m_rootPath = m_uncPath; + else + m_rootPath = m_uncPath.substring(0, pos); + } + } + + // Return the folder status + + return isAlfrescoFolder(); +} + /** * Class constructor * diff --git a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java index dd932b0694..7f90e141eb 100644 --- a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java @@ -41,6 +41,7 @@ import org.alfresco.filesys.smb.server.SMBSrvException; import org.alfresco.filesys.smb.server.SMBSrvPacket; import org.alfresco.filesys.smb.server.SMBSrvSession; import org.alfresco.filesys.smb.server.SecurityMode; +import org.alfresco.filesys.smb.server.VirtualCircuit; import org.alfresco.filesys.smb.server.repo.ContentContext; import org.alfresco.filesys.util.DataPacker; import org.alfresco.filesys.util.HexDump; @@ -491,8 +492,6 @@ public abstract class CifsAuthenticator // Authenticate the user - boolean isGuest = false; - int sts = authenticateUser(client, sess, CifsAuthenticator.NTLM1); if (sts > 0 && (sts & CifsAuthenticator.AUTH_GUEST) != 0) @@ -500,7 +499,7 @@ public abstract class CifsAuthenticator // Guest logon - isGuest = true; + client.setGuest( true); // DEBUG @@ -509,57 +508,55 @@ public abstract class CifsAuthenticator } else if (sts != CifsAuthenticator.AUTH_ALLOW) { + // DEBUG - // Check if the session already has valid client details and the new client details - // have null username/password values + if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("User " + user + ", access denied"); - if (sess.getClientInformation() != null && client.getUserName().length() == 0) - { + // Invalid user, reject the session setup request - // Use the existing client information details - - client = sess.getClientInformation(); - - // DEBUG - - if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) - logger.debug("Null client information, reusing existing client=" + client); - } - else - { - // DEBUG - - if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) - logger.debug("User " + user + ", access denied"); - - // Invalid user, reject the session setup request - - throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); - } + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); } else if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) { - + // Save the current user token in the client information + + if ( client.isNullSession() == false) + client.setAuthenticationToken( m_authComponent.getCurrentAuthentication()); + else + client.setAuthenticationToken( null); + // DEBUG logger.debug("User " + user + " logged on " + (client != null ? " (type " + client.getLogonTypeString() + ")" : "")); } - // Update the client information if not already set + // Create a virtual circuit and allocate a UID to the new circuit - if (sess.getClientInformation() == null - || sess.getClientInformation().getUserName().length() == 0) + VirtualCircuit vc = new VirtualCircuit( vcNum, client); + int uid = sess.addVirtualCircuit( vc); + + if ( uid == VirtualCircuit.InvalidUID) { - - // Set the client details for the session - - sess.setClientInformation(client); + // DEBUG + + if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("Failed to allocate UID for virtual circuit, " + vc); + + // Failed to allocate a UID + + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) { + + // DEBUG + + logger.debug("Allocated UID=" + uid + " for VC=" + vc); } - // Set the guest flag for the client, indicate that the session is logged on + // Indicate that the session is logged on - client.setGuest(isGuest); sess.setLoggedOn(true); // Build the session setup response SMB @@ -567,11 +564,11 @@ public abstract class CifsAuthenticator respPkt.setParameterCount(3); respPkt.setParameter(0, 0); // No chained response respPkt.setParameter(1, 0); // Offset to chained response - respPkt.setParameter(2, isGuest ? 1 : 0); + respPkt.setParameter(2, client.isGuest() ? 1 : 0); respPkt.setByteCount(0); respPkt.setTreeId(0); - respPkt.setUserId(0); + respPkt.setUserId(uid); // Set the various flags @@ -832,8 +829,7 @@ public abstract class CifsAuthenticator client.setGuest( true); - // Create a dynamic share for the guest user - // Create the disk driver and context + // Create a dynamic share for the guest user, create the disk driver and context DiskInterface diskDrv = m_config.getDiskInterface(); DiskDeviceContext diskCtx = new ContentContext(client.getUserName(), "", "", client.getHomeFolder()); @@ -936,4 +932,33 @@ public abstract class CifsAuthenticator return personName; } + + /** + * Set the current authenticated user context for this thread + * + * @param client ClientInfo + */ + public void setCurrentUser( ClientInfo client) + { + // Check the account type and setup the authentication context + + if ( client.isNullSession()) + { + // Clear the authentication, null user should not be allowed to do any service calls + + m_authComponent.clearCurrentSecurityContext(); + } + else if ( client.isGuest() == false) + { + // Set the authentication context for the request + + m_authComponent.setCurrentAuthentication( client.getAuthenticationToken()); + } + else + { + // Enable guest access for the request + + m_authComponent.setGuestUserAsCurrentUser(); + } + } } \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/ClientInfo.java b/source/java/org/alfresco/filesys/server/auth/ClientInfo.java index ab287f7897..e267a690ee 100644 --- a/source/java/org/alfresco/filesys/server/auth/ClientInfo.java +++ b/source/java/org/alfresco/filesys/server/auth/ClientInfo.java @@ -67,6 +67,10 @@ public class ClientInfo private String m_ipAddr; + // PID of the logon process for multi-stage logons + + private int m_pid = -1; + // Authentication token private Authentication m_authToken; @@ -394,6 +398,26 @@ public class ClientInfo return m_nfsAuthType; } + /** + * Return the process id + * + * @return int + */ + public final int getProcessId() + { + return m_pid; + } + + /** + * Set the process id + * + * @param pid int + */ + public final void setProcessId( int pid) + { + m_pid = pid; + } + /** * Set the remote users domain * diff --git a/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java index 4f034ee1c7..460cf77b3e 100644 --- a/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java @@ -60,6 +60,7 @@ import org.alfresco.filesys.smb.SMBStatus; import org.alfresco.filesys.smb.server.SMBSrvException; import org.alfresco.filesys.smb.server.SMBSrvPacket; import org.alfresco.filesys.smb.server.SMBSrvSession; +import org.alfresco.filesys.smb.server.VirtualCircuit; import org.alfresco.filesys.util.DataPacker; import org.alfresco.filesys.util.HexDump; import org.alfresco.repo.security.authentication.NTLMMode; @@ -507,29 +508,17 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca if ( reqPkt.getParameterCount() == 13) { - try - { - // Process the hashed password session setup + // Process the hashed password session setup - doHashedPasswordLogon( sess, reqPkt, respPkt); - return; - } - catch (SMBSrvException ex) - { - // Cleanup any stored context - - sess.setSetupObject( null); - - // Rethrow the exception - - throw ex; - } + doHashedPasswordLogon( sess, reqPkt, respPkt); + return; } // Extract the session details int maxBufSize = reqPkt.getParameter(2); int maxMpx = reqPkt.getParameter(3); + int vcNum = reqPkt.getParameter(4); int secBlobLen = reqPkt.getParameter(7); int capabs = reqPkt.getParameterLong(10); @@ -601,9 +590,13 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca if ( sess.hasRemoteAddress()) client.setClientAddress(sess.getRemoteAddress().getHostAddress()); - // Save the setup object, if valid + // Set the process id for this client, for multi-stage logons - Object setupObj = sess.getSetupObject(); + client.setProcessId( reqPkt.getProcessId()); + + // Get the current sesion setup object, or null + + Object setupObj = sess.getSetupObject( client.getProcessId()); // Process the security blob @@ -646,7 +639,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca { // Cleanup any stored context - sess.setSetupObject( null); + sess.removeSetupObject( client.getProcessId()); // Rethrow the exception @@ -675,15 +668,23 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Check if there is/was a session setup object stored in the session, this indicates a multi-stage session // setup so set the status code accordingly - if ( useRawNTLMSSP() || isNTLMSSP == true || sess.hasSetupObject() || setupObj != null) + boolean loggedOn = false; + + if ( useRawNTLMSSP() || isNTLMSSP == true || sess.hasSetupObject( client.getProcessId()) || setupObj != null) { // NTLMSSP has two stages, if there is a stored setup object then indicate more processing // required - if ( sess.hasSetupObject()) + if ( sess.hasSetupObject( client.getProcessId())) respPkt.setLongErrorCode( SMBStatus.NTMoreProcessingRequired); else + { respPkt.setLongErrorCode( SMBStatus.NTSuccess); + + // Indicate that the user is logged on + + loggedOn = true; + } respPkt.setParameterCount(4); respPkt.setParameter(0, 0xFF); // No chained response @@ -712,6 +713,44 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // security blob length respPkt.setParameterLong(8, 0); // reserved respPkt.setParameterLong(10, getServerCapabilities()); + + // Indicate that the user is logged on + + loggedOn = true; + } + + // If the user is logged on then allocate a virtual circuit + + int uid = 0; + + if ( loggedOn == true) { + + // Clear any stored session setup object for the logon + + sess.removeSetupObject( client.getProcessId()); + + // Create a virtual circuit for the new logon + + VirtualCircuit vc = new VirtualCircuit( vcNum, client); + uid = sess.addVirtualCircuit( vc); + + if ( uid == VirtualCircuit.InvalidUID) + { + // DEBUG + + if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("Failed to allocate UID for virtual circuit, " + vc); + + // Failed to allocate a UID + + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) { + + // DEBUG + + logger.debug("Allocated UID=" + uid + " for VC=" + vc); + } } // Common session setup response @@ -719,8 +758,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca respPkt.setCommand( reqPkt.getCommand()); respPkt.setByteCount(0); - respPkt.setTreeId(0); - respPkt.setUserId(0); + respPkt.setTreeId( 0); + respPkt.setUserId( uid); // Set the various flags @@ -834,7 +873,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Store the type 2 message in the session until the session setup is complete - sess.setSetupObject( type2Msg); + sess.setSetupObject( client.getProcessId(), type2Msg); // Set the response blob using the type 2 message @@ -848,11 +887,11 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Make sure a type 2 message was stored in the first stage of the session setup - if ( sess.hasSetupObject() == false || sess.getSetupObject() instanceof Type2NTLMMessage == false) + if ( sess.hasSetupObject( client.getProcessId()) == false || sess.getSetupObject( client.getProcessId()) instanceof Type2NTLMMessage == false) { // Clear the setup object - sess.setSetupObject( null); + sess.removeSetupObject( client.getProcessId()); // Return a logon failure @@ -937,7 +976,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca NegTokenTarg negTarg = null; - if ( tokType == SPNEGO.NegTokenTarg && sess.hasSetupObject() && sess.getSetupObject() instanceof Type2NTLMMessage) + if ( tokType == SPNEGO.NegTokenTarg && sess.hasSetupObject( client.getProcessId()) && sess.getSetupObject( client.getProcessId()) instanceof Type2NTLMMessage) { // Get the NTLMSSP blob from the NegTokenTarg blob @@ -972,7 +1011,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca int spnegoSts = SPNEGO.AcceptCompleted; - if ( sess.hasSetupObject()) + if ( sess.hasSetupObject( client.getProcessId())) spnegoSts = SPNEGO.AcceptIncomplete; // Package the NTLMSSP response in an SPNEGO response @@ -1022,7 +1061,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca int spnegoSts = SPNEGO.AcceptCompleted; - if ( sess.hasSetupObject()) + if ( sess.hasSetupObject( client.getProcessId())) spnegoSts = SPNEGO.AcceptIncomplete; // Package the NTLMSSP response in an SPNEGO response @@ -1183,8 +1222,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca // Get the type 2 message that contains the challenge sent to the client - Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject(); - sess.setSetupObject( null); + Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject( client.getProcessId()); + sess.removeSetupObject( client.getProcessId()); // Check if we are using local MD4 password hashes or passthru authentication @@ -1432,8 +1471,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca { // Get the type 2 message that contains the challenge sent to the client - Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject(); - sess.setSetupObject( null); + Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject( client.getProcessId()); + sess.removeSetupObject( client.getProcessId()); // Check if we are using local MD4 password hashes or passthru authentication @@ -1677,8 +1716,8 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca { // Get the type 2 message that contains the challenge sent to the client - Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject(); - sess.setSetupObject( null); + Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject( client.getProcessId()); + sess.removeSetupObject( client.getProcessId()); // Check if we are using local MD4 password hashes or passthru authentication @@ -1980,17 +2019,30 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca logger.debug("User " + user + ", logged on as guest"); } - // Update the client information if not already set + // Create a virtual circuit and allocate a UID to the new circuit - if (sess.getClientInformation() == null - || sess.getClientInformation().getUserName().length() == 0) + VirtualCircuit vc = new VirtualCircuit( vcNum, client); + int uid = sess.addVirtualCircuit( vc); + + if ( uid == VirtualCircuit.InvalidUID) { - - // Set the client details for the session - - sess.setClientInformation(client); + + // DEBUG + + if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("Failed to allocate UID for virtual circuit, " + vc); + + // Failed to allocate a UID + + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); } - + else if ( logger.isDebugEnabled() && sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) + { + // DEBUG + + logger.debug("Allocated UID=" + uid + " for VC=" + vc); + } + // Set the guest flag for the client, indicate that the session is logged on client.setGuest(isGuest); @@ -2005,7 +2057,7 @@ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements Ca respPkt.setByteCount(0); respPkt.setTreeId(0); - respPkt.setUserId(0); + respPkt.setUserId(uid); // Set the various flags diff --git a/source/java/org/alfresco/filesys/smb/server/CoreProtocolHandler.java b/source/java/org/alfresco/filesys/smb/server/CoreProtocolHandler.java index 0824d388e3..355a2a990b 100644 --- a/source/java/org/alfresco/filesys/smb/server/CoreProtocolHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/CoreProtocolHandler.java @@ -197,7 +197,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -310,7 +310,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -416,7 +416,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -539,7 +539,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -684,7 +684,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -802,7 +802,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -843,9 +843,6 @@ class CoreProtocolHandler extends ProtocolHandler // Access the disk interface and delete the file(s) - int fid; - NetworkFile netFile = null; - try { @@ -913,7 +910,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1084,7 +1081,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1185,7 +1182,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1316,8 +1313,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1484,8 +1480,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1557,7 +1552,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1714,8 +1709,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1769,8 +1763,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1906,7 +1899,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -1971,9 +1964,6 @@ class CoreProtocolHandler extends ProtocolHandler // Access the disk interface and rename the requested file - int fid; - NetworkFile netFile = null; - try { @@ -2021,7 +2011,6 @@ class CoreProtocolHandler extends ProtocolHandler */ protected final void procSearch(SMBSrvPacket outPkt) throws java.io.IOException, SMBSrvException { - // Check that the received packet looks like a valid search request if (m_smbPkt.checkPacketIsValid(2, 5) == false) @@ -2030,10 +2019,18 @@ class CoreProtocolHandler extends ProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } + // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection( m_smbPkt.getTreeId()); if (conn == null) { @@ -2146,7 +2143,7 @@ class CoreProtocolHandler extends ProtocolHandler // Allocate a search slot for the new search - searchId = m_sess.allocateSearchSlot(); + searchId = vc.allocateSearchSlot(); if (searchId == -1) { @@ -2158,7 +2155,7 @@ class CoreProtocolHandler extends ProtocolHandler // is started but never continued. int idx = 0; - ctx = m_sess.getSearchContext(idx); + ctx = vc.getSearchContext(idx); while (ctx != null && searchId == -1) { @@ -2175,18 +2172,18 @@ class CoreProtocolHandler extends ProtocolHandler // Deallocate the search context - m_sess.deallocateSearchSlot(idx); + vc.deallocateSearchSlot(idx); // Allocate the slot for the new search - searchId = m_sess.allocateSearchSlot(); + searchId = vc.allocateSearchSlot(); } else { // Update the search index and get the next search context - ctx = m_sess.getSearchContext(++idx); + ctx = vc.getSearchContext(++idx); } } @@ -2216,13 +2213,13 @@ class CoreProtocolHandler extends ProtocolHandler // Store details of the search in the context - ctx.setTreeId(treeId); + ctx.setTreeId(m_smbPkt.getTreeId()); ctx.setMaximumFiles(maxFiles); } // Save the search context - m_sess.setSearchContext(searchId, ctx); + vc.setSearchContext(searchId, ctx); } else { @@ -2236,7 +2233,7 @@ class CoreProtocolHandler extends ProtocolHandler int id = CoreResumeKey.getServerArea(resumeKey, 0); searchId = (id & 0xFFFF0000) >> 16; - ctx = m_sess.getSearchContext(searchId); + ctx = vc.getSearchContext(searchId); // Check if the search context is valid @@ -2277,7 +2274,7 @@ class CoreProtocolHandler extends ProtocolHandler // Release the search context - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); return; } } @@ -2293,7 +2290,7 @@ class CoreProtocolHandler extends ProtocolHandler // Check that the search context and tree connection match - if (ctx.getTreeId() != treeId) + if (ctx.getTreeId() != m_smbPkt.getTreeId()) { m_sess.sendErrorResponseSMB(SMBStatus.SRVInvalidTID, SMBStatus.ErrSrv); return; @@ -2432,7 +2429,7 @@ class CoreProtocolHandler extends ProtocolHandler // Release the search context - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); } else { @@ -2466,7 +2463,7 @@ class CoreProtocolHandler extends ProtocolHandler // Release the search context - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); } } } @@ -2481,8 +2478,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -2605,8 +2601,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -2646,7 +2641,6 @@ class CoreProtocolHandler extends ProtocolHandler // Seek to the specified position within the file - byte[] buf = outPkt.getBuffer(); long pos = 0; try @@ -2702,36 +2696,9 @@ class CoreProtocolHandler extends ProtocolHandler protected void procSessionSetup(SMBSrvPacket outPkt) throws SMBSrvException, IOException, TooManyConnectionsException { - - // Build the session setup response SMB - - outPkt.setParameterCount(3); - outPkt.setParameter(0, 0); - outPkt.setParameter(1, 0); - outPkt.setParameter(2, 8192); - outPkt.setByteCount(0); - - outPkt.setTreeId(0); - outPkt.setUserId(0); - - // Pack the OS, dialect and domain name strings. - - int pos = outPkt.getByteOffset(); - byte[] buf = outPkt.getBuffer(); - - pos = DataPacker.putString("Java", buf, pos, true); - pos = DataPacker.putString("JLAN Server " + m_sess.getServer().isVersion(), buf, pos, true); - pos = DataPacker.putString(m_sess.getServer().getConfiguration().getDomainName(), buf, pos, true); - - outPkt.setByteCount(pos - outPkt.getByteOffset()); - - // Send the negotiate response - - m_sess.sendResponseSMB(outPkt); - - // Update the session state - - m_sess.setState(SMBSrvSessionState.SMBSESSION); + // Return an access denied error, require a logon + + m_sess.sendErrorResponseSMB(SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); } /** @@ -2756,7 +2723,7 @@ class CoreProtocolHandler extends ProtocolHandler // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -2869,8 +2836,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -2998,6 +2964,15 @@ class CoreProtocolHandler extends ProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } + // Get the data bytes position and length int dataPos = m_smbPkt.getByteOffset(); @@ -3126,7 +3101,7 @@ class CoreProtocolHandler extends ProtocolHandler // Allocate a tree id for the new connection - int treeId = m_sess.addConnection(shareDev); + int treeId = vc.addConnection(shareDev); // Authenticate the share connection depending upon the security mode the server is running // under @@ -3152,7 +3127,7 @@ class CoreProtocolHandler extends ProtocolHandler // Set the file permission that this user has been granted for this share - TreeConnection tree = m_sess.findConnection(treeId); + TreeConnection tree = m_sess.findTreeConnection( m_smbPkt); tree.setPermission(filePerm); // Build the tree connect response @@ -3192,11 +3167,20 @@ class CoreProtocolHandler extends ProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } + // Get the tree id from the received packet and validate that it is a valid // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -3211,7 +3195,7 @@ class CoreProtocolHandler extends ProtocolHandler // Remove the specified connection from the session - m_sess.removeConnection(treeId); + vc.removeConnection(treeId, m_sess); // Build the tree disconnect response @@ -3247,8 +3231,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -3334,8 +3317,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { @@ -3470,8 +3452,7 @@ class CoreProtocolHandler extends ProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection( m_smbPkt); if (conn == null) { diff --git a/source/java/org/alfresco/filesys/smb/server/DCERPCHandler.java b/source/java/org/alfresco/filesys/smb/server/DCERPCHandler.java index fd362facc9..7364cd1f40 100644 --- a/source/java/org/alfresco/filesys/smb/server/DCERPCHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/DCERPCHandler.java @@ -60,8 +60,7 @@ public class DCERPCHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = srvTrans.getTreeId(); - TreeConnection conn = sess.findConnection(treeId); + TreeConnection conn = sess.findTreeConnection( srvTrans); if (conn == null) { @@ -187,12 +186,13 @@ public class DCERPCHandler * Process a DCE/RPC request * * @param sess SMBSrvSession + * @param vc VirtualCircuit * @param tbuf TransactBuffer * @param outPkt SMBSrvPacket * @exception IOException * @exception SMBSrvException */ - public static final void processDCERPCRequest(SMBSrvSession sess, TransactBuffer tbuf, SMBSrvPacket outPkt) + public static final void processDCERPCRequest(SMBSrvSession sess, VirtualCircuit vc, TransactBuffer tbuf, SMBSrvPacket outPkt) throws IOException, SMBSrvException { @@ -207,8 +207,7 @@ public class DCERPCHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = tbuf.getTreeId(); - TreeConnection conn = sess.findConnection(treeId); + TreeConnection conn = vc.findConnection( tbuf.getTreeId()); if (conn == null) { @@ -348,8 +347,7 @@ public class DCERPCHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = inPkt.getTreeId(); - TreeConnection conn = sess.findConnection(treeId); + TreeConnection conn = sess.findTreeConnection( inPkt); if (conn == null) { @@ -482,8 +480,7 @@ public class DCERPCHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = inPkt.getTreeId(); - TreeConnection conn = sess.findConnection(treeId); + TreeConnection conn = sess.findTreeConnection( inPkt); if (conn == null) { diff --git a/source/java/org/alfresco/filesys/smb/server/IPCHandler.java b/source/java/org/alfresco/filesys/smb/server/IPCHandler.java index 0637202b4f..712dce791a 100644 --- a/source/java/org/alfresco/filesys/smb/server/IPCHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/IPCHandler.java @@ -63,8 +63,7 @@ class IPCHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = smbPkt.getTreeId(); - TreeConnection conn = sess.findConnection(treeId); + TreeConnection conn = sess.findTreeConnection(smbPkt); if (conn == null) { @@ -75,7 +74,7 @@ class IPCHandler // Debug if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC)) - logger.debug("IPC$ Request [" + treeId + "] - cmd = " + smbPkt.getPacketTypeString()); + logger.debug("IPC$ Request [" + smbPkt.getTreeId() + "] - cmd = " + smbPkt.getPacketTypeString()); // Determine the SMB command @@ -136,11 +135,12 @@ class IPCHandler /** * Process an IPC$ transaction request. * + * @param vc VirtualCircuit * @param tbuf SrvTransactBuffer * @param sess SMBSrvSession * @param outPkt SMBSrvPacket */ - protected static void procTransaction(SrvTransactBuffer tbuf, SMBSrvSession sess, SMBSrvPacket outPkt) + protected static void procTransaction(VirtualCircuit vc, SrvTransactBuffer tbuf, SMBSrvSession sess, SMBSrvPacket outPkt) throws IOException, SMBSrvException { @@ -169,13 +169,13 @@ class IPCHandler // Set named pipe handle state case NamedPipeTransaction.SetNmPHandState: - procSetNamedPipeHandleState(sess, tbuf, outPkt); + procSetNamedPipeHandleState(sess, vc, tbuf, outPkt); break; // Named pipe transation request, pass the request to the DCE/RPC handler case NamedPipeTransaction.TransactNmPipe: - DCERPCHandler.processDCERPCRequest(sess, tbuf, outPkt); + DCERPCHandler.processDCERPCRequest(sess, vc, tbuf, outPkt); break; // Unknown command @@ -223,8 +223,7 @@ class IPCHandler // Get the tree connection details - int treeId = rxPkt.getTreeId(); - TreeConnection conn = sess.findConnection(treeId); + TreeConnection conn = sess.findTreeConnection(rxPkt); if (conn == null) { @@ -431,8 +430,7 @@ class IPCHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = rxPkt.getTreeId(); - TreeConnection conn = sess.findConnection(treeId); + TreeConnection conn = sess.findTreeConnection(rxPkt); if (conn == null) { @@ -454,7 +452,7 @@ class IPCHandler // Debug if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC)) - logger.debug("IPC$ File close [" + treeId + "] fid=" + fid); + logger.debug("IPC$ File close [" + rxPkt.getTreeId() + "] fid=" + fid); // Remove the file from the connections list of open files @@ -474,10 +472,11 @@ class IPCHandler * Process a set named pipe handle state request * * @param sess SMBSrvSession + * @param vc VirtualCircuit * @param tbuf SrvTransactBuffer * @param outPkt SMBSrvPacket */ - protected static void procSetNamedPipeHandleState(SMBSrvSession sess, SrvTransactBuffer tbuf, SMBSrvPacket outPkt) + protected static void procSetNamedPipeHandleState(SMBSrvSession sess, VirtualCircuit vc, SrvTransactBuffer tbuf, SMBSrvPacket outPkt) throws IOException, SMBSrvException { @@ -492,7 +491,7 @@ class IPCHandler // Get the connection for the request - TreeConnection conn = sess.findConnection(tbuf.getTreeId()); + TreeConnection conn = vc.findConnection(tbuf.getTreeId()); // Get the IPC pipe file for the specified file id @@ -536,8 +535,7 @@ class IPCHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = rxPkt.getTreeId(); - TreeConnection conn = sess.findConnection(treeId); + TreeConnection conn = sess.findTreeConnection(rxPkt); if (conn == null) { @@ -574,7 +572,7 @@ class IPCHandler // Debug if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_IPC)) - logger.debug("NT Create AndX [" + treeId + "] name=" + fileName + ", flags=0x" + logger.debug("NT Create AndX [" + rxPkt.getTreeId() + "] name=" + fileName + ", flags=0x" + Integer.toHexString(flags) + ", attr=0x" + Integer.toHexString(attrib) + ", allocSize=" + allocSize); diff --git a/source/java/org/alfresco/filesys/smb/server/LanManProtocolHandler.java b/source/java/org/alfresco/filesys/smb/server/LanManProtocolHandler.java index c956c1634f..396452b459 100644 --- a/source/java/org/alfresco/filesys/smb/server/LanManProtocolHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/LanManProtocolHandler.java @@ -191,6 +191,16 @@ class LanManProtocolHandler extends CoreProtocolHandler int flags = m_smbPkt.getAndXParameter(cmdOff, 2); int pwdLen = m_smbPkt.getAndXParameter(cmdOff, 3); + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( outPkt.getUserId()); + + if (vc == null) + { + outPkt.setError(m_smbPkt.isLongErrorCode(), SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return endOff; + } + // Get the data bytes position and length int dataPos = m_smbPkt.getAndXByteOffset(cmdOff); @@ -328,12 +338,12 @@ class LanManProtocolHandler extends CoreProtocolHandler // Allocate the tree id for this connection - int treeId = m_sess.addConnection(shareDev); + int treeId = vc.addConnection(shareDev); outPkt.setTreeId(treeId); // Set the file permission that this user has been granted for this share - TreeConnection tree = m_sess.findConnection(treeId); + TreeConnection tree = vc.findConnection(treeId); tree.setPermission(filePerm); // Inform the driver that a connection has been opened @@ -392,10 +402,19 @@ class LanManProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } + // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -420,7 +439,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Get the search context - SearchContext ctx = m_sess.getSearchContext(searchId); + SearchContext ctx = vc.getSearchContext(searchId); if (ctx == null) { @@ -438,7 +457,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Deallocate the search slot, close the search. - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Return a success status SMB @@ -463,8 +482,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -524,18 +542,37 @@ class LanManProtocolHandler extends CoreProtocolHandler */ protected final void procLogoffAndX(SMBSrvPacket outPkt) throws java.io.IOException, SMBSrvException { + // Check that the received packet looks like a valid logoff andX request - // Check that the received packet looks like a valid logoff andX request - - if (m_smbPkt.checkPacketIsValid(15, 1) == false) + if (m_smbPkt.checkPacketIsValid(2, 0) == false) { - m_sess.sendErrorResponseSMB(SMBStatus.SRVUnrecognizedCommand, SMBStatus.ErrSrv); - return; + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; } - // Return a success status SMB + // Get the virtual circuit for the request + + int uid = m_smbPkt.getUserId(); + VirtualCircuit vc = m_sess.findVirtualCircuit( uid); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } - m_sess.sendSuccessResponseSMB(); + // DEBUG + + if ( logger.isDebugEnabled() && m_sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("Logoff vc=" + vc); + + // Close the virtual circuit + + m_sess.removeVirtualCircuit( uid); + + // Return a success status SMB + + m_sess.sendSuccessResponseSMB(); } /** @@ -556,8 +593,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -631,7 +667,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("File Open AndX [" + treeId + "] params=" + params); + logger.debug("File Open AndX [" + m_smbPkt.getTreeId() + "] params=" + params); // Access the disk interface and open the requested file @@ -819,8 +855,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -981,8 +1016,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -1042,13 +1076,10 @@ class LanManProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("File Rename [" + treeId + "] old name=" + oldName + ", new name=" + newName); + logger.debug("File Rename [" + m_smbPkt.getTreeId() + "] old name=" + oldName + ", new name=" + newName); // Access the disk interface and rename the requested file - int fid; - NetworkFile netFile = null; - try { @@ -1339,11 +1370,19 @@ class LanManProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } + // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -1414,7 +1453,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Save the partial transaction data - m_sess.setTransaction(transBuf); + vc.setTransaction(transBuf); // Send an intermediate acknowedgement response @@ -1427,14 +1466,14 @@ class LanManProtocolHandler extends CoreProtocolHandler if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) { - IPCHandler.procTransaction(transBuf, m_sess, outPkt); + IPCHandler.procTransaction(vc, transBuf, m_sess, outPkt); return; } // DEBUG if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_TRAN)) - logger.debug("Transaction [" + treeId + "] tbuf=" + transBuf); + logger.debug("Transaction [" + m_smbPkt.getTreeId() + "] tbuf=" + transBuf); // Process the transaction buffer @@ -1461,11 +1500,20 @@ class LanManProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } + // Get the tree id from the received packet and validate that it is a valid // connection id. int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -1486,9 +1534,9 @@ class LanManProtocolHandler extends CoreProtocolHandler // Check if there is an active transaction, and it is an NT transaction - if (m_sess.hasTransaction() == false - || (m_sess.getTransaction().isType() == PacketType.Transaction && m_smbPkt.getCommand() != PacketType.TransactionSecond) - || (m_sess.getTransaction().isType() == PacketType.Transaction2 && m_smbPkt.getCommand() != PacketType.Transaction2Second)) + if (vc.hasTransaction() == false + || (vc.getTransaction().isType() == PacketType.Transaction && m_smbPkt.getCommand() != PacketType.TransactionSecond) + || (vc.getTransaction().isType() == PacketType.Transaction2 && m_smbPkt.getCommand() != PacketType.Transaction2Second)) { // No transaction to continue, or packet does not match the existing transaction, return @@ -1502,7 +1550,7 @@ class LanManProtocolHandler extends CoreProtocolHandler SMBSrvTransPacket tpkt = new SMBSrvTransPacket(m_smbPkt.getBuffer()); byte[] buf = tpkt.getBuffer(); - SrvTransactBuffer transBuf = m_sess.getTransaction(); + SrvTransactBuffer transBuf = vc.getTransaction(); // Append the parameter data to the transaction buffer, if any @@ -1551,14 +1599,14 @@ class LanManProtocolHandler extends CoreProtocolHandler // Clear the in progress transaction - m_sess.setTransaction(null); + 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(transBuf, m_sess, outPkt); + IPCHandler.procTransaction(vc, transBuf, m_sess, outPkt); return; } @@ -1644,11 +1692,18 @@ class LanManProtocolHandler extends CoreProtocolHandler protected final void procTrans2FindFirst(SrvTransactBuffer tbuf, SMBSrvPacket outPkt) throws java.io.IOException, SMBSrvException { + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -1705,7 +1760,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Allocate a search slot for the new search - searchId = m_sess.allocateSearchSlot(); + searchId = vc.allocateSearchSlot(); if (searchId == -1) { @@ -1744,7 +1799,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Save the search context - m_sess.setSearchContext(searchId, ctx); + vc.setSearchContext(searchId, ctx); // Create the reply transact buffer @@ -1859,7 +1914,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Release the search context - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); } } catch (FileNotFoundException ex) @@ -1868,12 +1923,11 @@ class LanManProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Search path does not exist m_sess.sendErrorResponseSMB(SMBStatus.DOSNoMoreFiles, SMBStatus.ErrDos); - // m_sess.sendErrorResponseSMB(SMBStatus.DOSFileNotFound, SMBStatus.ErrDos); } catch (InvalidDeviceInterfaceException ex) { @@ -1881,7 +1935,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Failed to get/initialize the disk interface @@ -1893,7 +1947,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Requested information level is not supported @@ -1912,11 +1966,18 @@ class LanManProtocolHandler extends CoreProtocolHandler protected final void procTrans2FindNext(SrvTransactBuffer tbuf, SMBSrvPacket outPkt) throws java.io.IOException, SMBSrvException { + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -1961,7 +2022,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Retrieve the search context - ctx = m_sess.getSearchContext(searchId); + ctx = vc.getSearchContext(searchId); if (ctx == null) { @@ -2091,7 +2152,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Release the search context - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); } } catch (FileNotFoundException ex) @@ -2100,12 +2161,11 @@ class LanManProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Search path does not exist m_sess.sendErrorResponseSMB(SMBStatus.DOSNoMoreFiles, SMBStatus.ErrDos); - // m_sess.sendErrorResponseSMB(SMBStatus.DOSFileNotFound, SMBStatus.ErrDos); } catch (InvalidDeviceInterfaceException ex) { @@ -2113,7 +2173,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Failed to get/initialize the disk interface @@ -2125,7 +2185,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Requested information level is not supported @@ -2147,8 +2207,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2317,8 +2376,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2452,6 +2510,14 @@ class LanManProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } + // Extract the parameters int flags = m_smbPkt.getParameter(2); @@ -2590,12 +2656,12 @@ class LanManProtocolHandler extends CoreProtocolHandler // Allocate a tree id for the new connection - int treeId = m_sess.addConnection(shareDev); + int treeId = vc.addConnection(shareDev); outPkt.setTreeId(treeId); // Set the file permission that this user has been granted for this share - TreeConnection tree = m_sess.findConnection(treeId); + TreeConnection tree = vc.findConnection(treeId); tree.setPermission(filePerm); // Debug @@ -2645,8 +2711,7 @@ class LanManProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2896,7 +2961,7 @@ class LanManProtocolHandler extends CoreProtocolHandler int treeId = m_smbPkt.getTreeId(); TreeConnection conn = null; if (treeId != -1) - conn = m_sess.findConnection(treeId); + conn = m_sess.findTreeConnection(m_smbPkt); if (conn != null) { diff --git a/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java b/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java index c8a6022dee..96d9e01a3e 100644 --- a/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java @@ -346,7 +346,7 @@ public class NTProtocolHandler extends CoreProtocolHandler int treeId = m_smbPkt.getTreeId(); TreeConnection conn = null; if (treeId != -1) - conn = m_sess.findConnection(treeId); + conn = m_sess.findTreeConnection(m_smbPkt); if (conn != null) { @@ -577,9 +577,18 @@ public class NTProtocolHandler extends CoreProtocolHandler // Extract the parameters -// int flags = m_smbPkt.getAndXParameter(cmdOff, 2); int pwdLen = m_smbPkt.getAndXParameter(cmdOff, 3); + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( outPkt.getUserId()); + + if (vc == null) + { + outPkt.setError(m_smbPkt.isLongErrorCode(), SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return endOff; + } + // Reset the byte pointer for data unpacking m_smbPkt.setBytePointer(m_smbPkt.getAndXByteOffset(cmdOff), m_smbPkt.getAndXByteCount(cmdOff)); @@ -776,12 +785,12 @@ public class NTProtocolHandler extends CoreProtocolHandler // Allocate the tree id for this connection - int treeId = m_sess.addConnection(shareDev); + int treeId = vc.addConnection(shareDev); outPkt.setTreeId(treeId); // Set the file permission that this user has been granted for this share - tree = m_sess.findConnection(treeId); + tree = vc.findConnection(treeId); tree.setPermission(sharePerm); // Inform the driver that a connection has been opened @@ -881,8 +890,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -996,8 +1004,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -1008,9 +1015,6 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the file id from the request int fid = m_smbPkt.getAndXParameter(cmdOff, 0); -// int ftime = m_smbPkt.getAndXParameter(cmdOff, 1); -// int fdate = m_smbPkt.getAndXParameter(cmdOff, 2); - NetworkFile netFile = conn.findFile(fid); if (netFile == null) @@ -1022,7 +1026,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("Chained File Close [" + treeId + "] fid=" + fid); + logger.debug("Chained File Close [" + m_smbPkt.getTreeId() + "] fid=" + fid); // Close the file @@ -1093,9 +1097,16 @@ public class NTProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + if ( vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } + // Extract the parameters -// int flags = m_smbPkt.getParameter(2); int pwdLen = m_smbPkt.getParameter(3); // Initialize the byte area pointer @@ -1291,12 +1302,12 @@ public class NTProtocolHandler extends CoreProtocolHandler // Allocate a tree id for the new connection - int treeId = m_sess.addConnection(shareDev); + int treeId = vc.addConnection(shareDev); outPkt.setTreeId(treeId); // Set the file permission that this user has been granted for this share - TreeConnection tree = m_sess.findConnection(treeId); + TreeConnection tree = vc.findConnection(treeId); tree.setPermission(sharePerm); // Debug @@ -1392,8 +1403,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -1415,8 +1425,6 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the file id from the request int fid = m_smbPkt.getParameter(0); -// int ftime = m_smbPkt.getParameter(1); -// int fdate = m_smbPkt.getParameter(2); NetworkFile netFile = conn.findFile(fid); @@ -1429,7 +1437,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("File close [" + treeId + "] fid=" + fid); + logger.debug("File close [" + m_smbPkt.getTreeId() + "] fid=" + fid); // Close the file @@ -1513,11 +1521,19 @@ public class NTProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(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 = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(m_smbPkt.getTreeId()); if (conn == null) { @@ -1588,7 +1604,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Save the partial transaction data - m_sess.setTransaction(transBuf); + vc.setTransaction(transBuf); // Send an intermediate acknowedgement response @@ -1601,14 +1617,14 @@ public class NTProtocolHandler extends CoreProtocolHandler if (conn.getSharedDevice().getType() == ShareType.ADMINPIPE) { - IPCHandler.procTransaction(transBuf, m_sess, outPkt); + IPCHandler.procTransaction(vc, transBuf, m_sess, outPkt); return; } // DEBUG if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_TRAN)) - logger.debug("Transaction [" + treeId + "] tbuf=" + transBuf); + logger.debug("Transaction [" + m_smbPkt.getTreeId() + "] tbuf=" + transBuf); // Process the transaction buffer @@ -1635,11 +1651,19 @@ public class NTProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(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 = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(m_smbPkt.getTreeId()); if (conn == null) { @@ -1660,9 +1684,9 @@ public class NTProtocolHandler extends CoreProtocolHandler // Check if there is an active transaction, and it is an NT transaction - if (m_sess.hasTransaction() == false - || (m_sess.getTransaction().isType() == PacketType.Transaction && m_smbPkt.getCommand() != PacketType.TransactionSecond) - || (m_sess.getTransaction().isType() == PacketType.Transaction2 && m_smbPkt.getCommand() != PacketType.Transaction2Second)) + if (vc.hasTransaction() == false + || (vc.getTransaction().isType() == PacketType.Transaction && m_smbPkt.getCommand() != PacketType.TransactionSecond) + || (vc.getTransaction().isType() == PacketType.Transaction2 && m_smbPkt.getCommand() != PacketType.Transaction2Second)) { // No transaction to continue, or packet does not match the existing transaction, return @@ -1676,7 +1700,7 @@ public class NTProtocolHandler extends CoreProtocolHandler SMBSrvTransPacket tpkt = new SMBSrvTransPacket(m_smbPkt.getBuffer()); byte[] buf = tpkt.getBuffer(); - SrvTransactBuffer transBuf = m_sess.getTransaction(); + SrvTransactBuffer transBuf = vc.getTransaction(); // Append the parameter data to the transaction buffer, if any @@ -1705,7 +1729,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_TRAN)) - logger.debug("Transaction Secondary [" + treeId + "] paramLen=" + plen + ", dataLen=" + dlen); + logger.debug("Transaction Secondary [" + m_smbPkt.getTreeId() + "] paramLen=" + plen + ", dataLen=" + dlen); // Check if the transaction has been received or there are more sections to be received @@ -1725,21 +1749,21 @@ public class NTProtocolHandler extends CoreProtocolHandler // Clear the in progress transaction - m_sess.setTransaction(null); + 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(transBuf, m_sess, outPkt); + IPCHandler.procTransaction(vc, transBuf, m_sess, outPkt); return; } // DEBUG if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_TRAN)) - logger.debug("Transaction second [" + treeId + "] tbuf=" + transBuf); + logger.debug("Transaction second [" + m_smbPkt.getTreeId() + "] tbuf=" + transBuf); // Process the transaction @@ -1843,10 +1867,18 @@ public class NTProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(m_smbPkt.getTreeId()); if (conn == null) { @@ -1871,7 +1903,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the search context - SearchContext ctx = m_sess.getSearchContext(searchId); + SearchContext ctx = vc.getSearchContext(searchId); if (ctx == null) { @@ -1889,7 +1921,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Deallocate the search slot, close the search. - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Return a success status SMB @@ -1914,8 +1946,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2121,10 +2152,37 @@ public class NTProtocolHandler extends CoreProtocolHandler */ protected final void procLogoffAndX(SMBSrvPacket outPkt) throws java.io.IOException, SMBSrvException { + // Check that the received packet looks like a valid logoff andX request - // Return a success status SMB + if (m_smbPkt.checkPacketIsValid(2, 0) == false) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + return; + } - m_sess.sendSuccessResponseSMB(); + // Get the virtual circuit for the request + + int uid = m_smbPkt.getUserId(); + VirtualCircuit vc = m_sess.findVirtualCircuit( uid); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + + // DEBUG + + if ( logger.isDebugEnabled() && m_sess.hasDebug( SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("Logoff vc=" + vc); + + // Close the virtual circuit + + m_sess.removeVirtualCircuit( uid); + + // Return a success status SMB + + m_sess.sendSuccessResponseSMB(); } /** @@ -2145,8 +2203,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2188,7 +2245,6 @@ public class NTProtocolHandler extends CoreProtocolHandler // Extract the open file parameters -// int flags = m_smbPkt.getParameter(2); int access = m_smbPkt.getParameter(3); int srchAttr = m_smbPkt.getParameter(4); int fileAttr = m_smbPkt.getParameter(5); @@ -2217,7 +2273,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("File Open AndX [" + treeId + "] params=" + params); + logger.debug("File Open AndX [" + m_smbPkt.getTreeId() + "] params=" + params); // Access the disk interface and open the requested file @@ -2395,8 +2451,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2592,8 +2647,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2653,7 +2707,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("File Rename [" + treeId + "] old name=" + oldName + ", new name=" + newName); + logger.debug("File Rename [" + m_smbPkt.getTreeId() + "] old name=" + oldName + ", new name=" + newName); // Access the disk interface and rename the requested file @@ -2748,8 +2802,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2794,7 +2847,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("File Delete [" + treeId + "] name=" + fileName); + logger.debug("File Delete [" + m_smbPkt.getTreeId() + "] name=" + fileName); // Access the disk interface and delete the file(s) @@ -2871,8 +2924,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree id from the received packet and validate that it is a valid // connection id. - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -2917,7 +2969,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("Directory Delete [" + treeId + "] name=" + dirName); + logger.debug("Directory Delete [" + m_smbPkt.getTreeId() + "] name=" + dirName); // Access the disk interface and delete the directory @@ -2992,11 +3044,19 @@ public class NTProtocolHandler extends CoreProtocolHandler protected final void procTrans2FindFirst(SrvTransactBuffer tbuf, SMBSrvPacket outPkt) throws java.io.IOException, SMBSrvException { + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(tbuf.getTreeId()); if (conn == null) { @@ -3073,7 +3133,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Allocate a search slot for the new search - searchId = m_sess.allocateSearchSlot(); + searchId = vc.allocateSearchSlot(); if (searchId == -1) { @@ -3111,7 +3171,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Store details of the search in the context - ctx.setTreeId(treeId); + ctx.setTreeId(m_smbPkt.getTreeId()); ctx.setMaximumFiles(maxFiles); } else @@ -3125,7 +3185,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Save the search context - m_sess.setSearchContext(searchId, ctx); + vc.setSearchContext(searchId, ctx); // Create the reply transact buffer @@ -3315,7 +3375,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Release the search context - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); } } catch (FileNotFoundException ex) @@ -3331,7 +3391,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Requested path does not exist @@ -3344,7 +3404,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Failed to get/initialize the disk interface @@ -3356,7 +3416,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Requested information level is not supported @@ -3376,10 +3436,18 @@ public class NTProtocolHandler extends CoreProtocolHandler SMBSrvException { + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(tbuf.getTreeId()); if (conn == null) { @@ -3418,7 +3486,7 @@ public class NTProtocolHandler extends CoreProtocolHandler { // Retrieve the search context - ctx = m_sess.getSearchContext(searchId); + ctx = vc.getSearchContext(searchId); if (ctx == null) { @@ -3562,7 +3630,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Release the search context - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); } } catch (FileNotFoundException ex) @@ -3571,7 +3639,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Search path does not exist @@ -3583,7 +3651,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Deallocate the search if (searchId != -1) - m_sess.deallocateSearchSlot(searchId); + vc.deallocateSearchSlot(searchId); // Requested information level is not supported @@ -3603,10 +3671,20 @@ public class NTProtocolHandler extends CoreProtocolHandler throws java.io.IOException, SMBSrvException { - // Get the tree connection details + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + + // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -3843,10 +3921,20 @@ public class NTProtocolHandler extends CoreProtocolHandler SMBSrvException { - // Get the tree connection details + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + + // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -4049,11 +4137,20 @@ public class NTProtocolHandler extends CoreProtocolHandler protected final void procTrans2QueryFile(SrvTransactBuffer tbuf, SMBSrvPacket outPkt) throws java.io.IOException, SMBSrvException { + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } - // Get the tree connection details + // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -4256,10 +4353,20 @@ public class NTProtocolHandler extends CoreProtocolHandler SMBSrvException { - // Get the tree connection details + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + + // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -4660,10 +4767,20 @@ public class NTProtocolHandler extends CoreProtocolHandler SMBSrvException { - // Get the tree connection details + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + + // Get the tree connection details int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -4911,8 +5028,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -5124,8 +5240,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -5246,7 +5361,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("NT Create AndX [" + treeId + "] params=" + params); + logger.debug("NT Create AndX [" + m_smbPkt.getTreeId() + "] params=" + params); // Access the disk interface and open the requested file @@ -5376,7 +5491,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug(" [" + treeId + "] name=" + fileName + " truncated"); + logger.debug(" [" + m_smbPkt.getTreeId() + "] name=" + fileName + " truncated"); } // Set the file action response @@ -5590,8 +5705,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Get the tree connection details - int treeId = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = m_sess.findTreeConnection(m_smbPkt); if (conn == null) { @@ -5670,11 +5784,19 @@ public class NTProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(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 = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(m_smbPkt.getTreeId()); if (conn == null) { @@ -5744,7 +5866,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_TRAN)) - logger.debug("NT Transaction [" + treeId + "] transbuf=" + transBuf); + logger.debug("NT Transaction [" + m_smbPkt.getTreeId() + "] transbuf=" + transBuf); // Append the setup, parameter and data blocks to the transaction data @@ -5757,7 +5879,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_TRAN)) - logger.debug("NT Transaction [" + treeId + "] pcnt=" + ntTrans.getNTParameter(4) + ", offset=" + logger.debug("NT Transaction [" + m_smbPkt.getTreeId() + "] pcnt=" + ntTrans.getNTParameter(4) + ", offset=" + ntTrans.getNTParameter(5)); cnt = ntTrans.getParameterBlockCount(); @@ -5773,7 +5895,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_TRAN)) - logger.debug("NT Transaction [" + treeId + "] cmd=0x" + Integer.toHexString(subCmd) + ", multiPkt=" + logger.debug("NT Transaction [" + m_smbPkt.getTreeId() + "] cmd=0x" + Integer.toHexString(subCmd) + ", multiPkt=" + transBuf.isMultiPacket()); // Check for a multi-packet transaction, for a multi-packet transaction we just acknowledge @@ -5785,7 +5907,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Save the partial transaction data - m_sess.setTransaction(transBuf); + vc.setTransaction(transBuf); // Send an intermediate acknowedgement response @@ -5817,11 +5939,19 @@ public class NTProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(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 = m_smbPkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(m_smbPkt.getTreeId()); if (conn == null) { @@ -5850,7 +5980,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Check if there is an active transaction, and it is an NT transaction - if (m_sess.hasTransaction() == false || m_sess.getTransaction().isType() != PacketType.NTTransact) + if (vc.hasTransaction() == false || vc.getTransaction().isType() != PacketType.NTTransact) { // No NT transaction to continue, return an error @@ -5863,7 +5993,7 @@ public class NTProtocolHandler extends CoreProtocolHandler NTTransPacket ntTrans = new NTTransPacket(m_smbPkt.getBuffer()); byte[] buf = ntTrans.getBuffer(); - SrvTransactBuffer transBuf = m_sess.getTransaction(); + SrvTransactBuffer transBuf = vc.getTransaction(); // Append the parameter data to the transaction buffer, if any @@ -5892,7 +6022,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_TRAN)) - logger.debug("NT Transaction Secondary [" + treeId + "] paramLen=" + plen + ", dataLen=" + dlen); + logger.debug("NT Transaction Secondary [" + m_smbPkt.getTreeId() + "] paramLen=" + plen + ", dataLen=" + dlen); // Check if the transaction has been received or there are more sections to be received @@ -5912,7 +6042,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Clear the in progress transaction - m_sess.setTransaction(null); + vc.setTransaction(null); // Process the transaction @@ -6020,10 +6150,18 @@ public class NTProtocolHandler extends CoreProtocolHandler return; } + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + // Get the tree connection details - int treeId = tbuf.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(m_smbPkt.getTreeId()); if (conn == null) { @@ -6140,7 +6278,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug("NT TransactCreate [" + treeId + "] params=" + params + " secDescLen=" + sdLen + logger.debug("NT TransactCreate [" + m_smbPkt.getTreeId() + "] params=" + params + " secDescLen=" + sdLen + ", extAttribLen=" + eaLen); // Access the disk interface and open/create the requested file @@ -6254,7 +6392,7 @@ public class NTProtocolHandler extends CoreProtocolHandler // Debug if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE)) - logger.debug(" [" + treeId + "] name=" + fileName + " truncated"); + logger.debug(" [" + m_smbPkt.getTreeId() + "] name=" + fileName + " truncated"); } // Set the file action response @@ -6420,11 +6558,18 @@ public class NTProtocolHandler extends CoreProtocolHandler protected final void procNTTransactIOCtl(SrvTransactBuffer tbuf, NTTransPacket outPkt) throws IOException, SMBSrvException { + // Get the virtual circuit for the request + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + // Get the tree connection details - int treeId = tbuf.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(m_smbPkt.getTreeId()); if (conn == null) { m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); @@ -6531,11 +6676,18 @@ public class NTProtocolHandler extends CoreProtocolHandler protected final void procNTTransactQuerySecurityDesc(SrvTransactBuffer tbuf, NTTransPacket outPkt) throws IOException, SMBSrvException { + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } // Get the tree connection details - int treeId = tbuf.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(tbuf.getTreeId()); if (conn == null) { @@ -6604,10 +6756,18 @@ public class NTProtocolHandler extends CoreProtocolHandler DataBuffer paramBuf = tbuf.getParameterBuffer(); + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } + // Get the tree connection details - int treeId = tbuf.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(tbuf.getTreeId()); if (conn == null) { @@ -6653,11 +6813,19 @@ public class NTProtocolHandler extends CoreProtocolHandler protected final void procNTTransactNotifyChange(NTTransPacket ntpkt, SMBSrvPacket outPkt) throws IOException, SMBSrvException { + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } - // Get the tree connection details + // Get the tree connection details int treeId = ntpkt.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { @@ -6809,15 +6977,24 @@ public class NTProtocolHandler extends CoreProtocolHandler protected final void procNTTransactRename(SrvTransactBuffer tbuf, NTTransPacket outPkt) throws IOException, SMBSrvException { + // Unpack the request details + + DataBuffer paramBuf = tbuf.getParameterBuffer(); + + // Get the virtual circuit for the request + + VirtualCircuit vc = m_sess.findVirtualCircuit( m_smbPkt.getUserId()); + + if (vc == null) + { + m_sess.sendErrorResponseSMB(SMBStatus.NTInvalidParameter, SMBStatus.DOSInvalidDrive, SMBStatus.ErrDos); + return; + } - // Unpack the request details + // Get the tree connection details -// DataBuffer paramBuf = tbuf.getParameterBuffer(); - - // Get the tree connection details - - int treeId = tbuf.getTreeId(); - TreeConnection conn = m_sess.findConnection(treeId); + int treeId = tbuf.getTreeId(); + TreeConnection conn = vc.findConnection(treeId); if (conn == null) { diff --git a/source/java/org/alfresco/filesys/smb/server/SMBSrvException.java b/source/java/org/alfresco/filesys/smb/server/SMBSrvException.java index cad98220fc..ac61bfc44f 100644 --- a/source/java/org/alfresco/filesys/smb/server/SMBSrvException.java +++ b/source/java/org/alfresco/filesys/smb/server/SMBSrvException.java @@ -39,7 +39,7 @@ public class SMBSrvException extends Exception // NT 32-bit error code - protected int m_NTerror; + protected int m_NTerror = -1; /** * Construct an SMB exception with the specified error class/error code. @@ -116,6 +116,16 @@ public class SMBSrvException extends Exception return m_errorcode; } + /** + * Check if the NT error code has been set + * + * @return boolean + */ + public final boolean hasNTErrorCode() + { + return m_NTerror != -1 ? true : false; + } + /** * Return the NT error code * diff --git a/source/java/org/alfresco/filesys/smb/server/SMBSrvSession.java b/source/java/org/alfresco/filesys/smb/server/SMBSrvSession.java index fa924a6944..a9560a8ad9 100644 --- a/source/java/org/alfresco/filesys/smb/server/SMBSrvSession.java +++ b/source/java/org/alfresco/filesys/smb/server/SMBSrvSession.java @@ -31,12 +31,8 @@ import org.alfresco.filesys.netbios.RFCNetBIOSProtocol; import org.alfresco.filesys.server.SrvSession; import org.alfresco.filesys.server.auth.AuthenticatorException; import org.alfresco.filesys.server.auth.CifsAuthenticator; -import org.alfresco.filesys.server.core.DeviceInterface; -import org.alfresco.filesys.server.core.SharedDevice; import org.alfresco.filesys.server.filesys.DiskDeviceContext; -import org.alfresco.filesys.server.filesys.DiskInterface; import org.alfresco.filesys.server.filesys.NetworkFile; -import org.alfresco.filesys.server.filesys.SearchContext; import org.alfresco.filesys.server.filesys.TooManyConnectionsException; import org.alfresco.filesys.server.filesys.TreeConnection; import org.alfresco.filesys.smb.Capability; @@ -80,15 +76,6 @@ public class SMBSrvSession extends SrvSession implements Runnable public static final int DefaultConnections = 4; public static final int MaxConnections = 16; - // Tree ids are 16bit values - - private static final int TreeIdMask = 0x0000FFFF; - - // Default and maximum number of search slots - - private static final int DefaultSearches = 8; - private static final int MaxSearches = 256; - // Maximum multiplexed packets allowed (client can send up to this many SMBs before waiting for // a response) // @@ -131,20 +118,6 @@ public class SMBSrvSession extends SrvSession implements Runnable private String m_callerNBName; private String m_targetNBName; - // Connected share list and next tree id - - private Hashtable m_connections; - private int m_treeId; - - // Active search list for this session - - private SearchContext[] m_search; - private int m_searchCount; - - // Active transaction details - - private SrvTransactBuffer m_transact; - // Notify change requests and notifications pending flag private NotifyRequestList m_notifyList; @@ -173,9 +146,13 @@ public class SMBSrvSession extends SrvSession implements Runnable private int m_clientCaps; - // Session setup object, temporarily stored by an authenticator when the authentication is multi-stage + // Virtual circuit list - private Object m_setupObject; + private VirtualCircuitList m_vcircuits; + + // Setup objects used during two stage session setup before the virtual circuit is allocated + + private Hashtable m_setupObjects; // Debug flag values @@ -233,6 +210,10 @@ public class SMBSrvSession extends SrvSession implements Runnable if (handler.hasClientName()) m_callerNBName = handler.getClientName(); } + + // Allocate the virtual circuit list + + m_vcircuits = new VirtualCircuitList(); } /** @@ -246,201 +227,127 @@ public class SMBSrvSession extends SrvSession implements Runnable } /** - * Add a new connection to this session. Return the allocated tree id for the new connection. + * Find the tree connection for the request * - * @return int Allocated tree id (connection id). - * @param shrDev SharedDevice + * @param smbPkt SMBSrvPacket + * @return TreeConnection */ - protected int addConnection(SharedDevice shrDev) throws TooManyConnectionsException - { + public final TreeConnection findTreeConnection(SMBSrvPacket smbPkt) { - // Check if the connection array has been allocated + // Find the virtual circuit for the request - if (m_connections == null) - m_connections = new Hashtable(DefaultConnections); + TreeConnection tree = null; + VirtualCircuit vc = findVirtualCircuit( smbPkt.getUserId()); - // Allocate an id for the tree connection + if (vc != null) { - int treeId = 0; + // Find the tree connection - synchronized (m_connections) - { + tree = vc.findConnection(smbPkt.getTreeId()); + } - // Check if the tree connection table is full - - if (m_connections.size() == MaxConnections) - throw new TooManyConnectionsException(); - - // Find a free slot in the connection array - - treeId = (m_treeId++ & TreeIdMask); - Integer key = new Integer(treeId); - - while (m_connections.contains(key)) - { - - // Try another tree id for the new connection - - treeId = (m_treeId++ & TreeIdMask); - key = new Integer(treeId); - } - - // Store the new tree connection - - m_connections.put(key, new TreeConnection(shrDev)); - } - - // Return the allocated tree id - - return treeId; - } + // Return the tree connection, or null if invalid UID or TID + return tree; + } + /** - * Allocate a slot in the active searches list for a new search. - * - * @return int Search slot index, or -1 if there are no more search slots available. - */ - protected final int allocateSearchSlot() - { - - // Check if the search array has been allocated - - if (m_search == null) - m_search = new SearchContext[DefaultSearches]; - - // Find a free slot for the new search - - int idx = 0; - - while (idx < m_search.length && m_search[idx] != null) - idx++; - - // Check if we found a free slot - - if (idx == m_search.length) - { - - // The search array needs to be extended, check if we reached the limit. - - if (m_search.length >= MaxSearches) - return -1; - - // Extend the search array - - SearchContext[] newSearch = new SearchContext[m_search.length * 2]; - System.arraycopy(m_search, 0, newSearch, 0, m_search.length); - m_search = newSearch; - } - - // Return the allocated search slot index - - m_searchCount++; - return idx; + * Add a new virtual circuit, return the allocated UID + * + * @param vc + * VirtualCircuit + * @return int + */ + public final int addVirtualCircuit( VirtualCircuit vc) { + + // Add the new virtual circuit + + return m_vcircuits.addCircuit( vc); } - + /** - * Cleanup any resources owned by this session, close files, searches and change notification - * requests. - */ + * Find a virtual circuit with the allocated UID + * + * @param uid int + * @return VirtualCircuit + */ + public final VirtualCircuit findVirtualCircuit(int uid) { + + // Find the virtual circuit with the specified UID + + VirtualCircuit vc = m_vcircuits.findCircuit(uid); + if (vc != null) { + + // Set the session client information from the virtual circuit + + setClientInformation(vc.getClientInformation()); + + // Set the current authenticated user for this request + + getSMBServer().getAuthenticator().setCurrentUser( vc.getClientInformation()); + } + + // Return the virtual circuit + + return vc; + } + + /** + * Remove a virtual circuit + * + * @param uid + * int + */ + public final void removeVirtualCircuit(int uid) { + + // Remove the virtual circuit with the specified UID + + m_vcircuits.removeCircuit(uid, this); + } + + /** + * Cleanup any resources owned by this session, close files, searches and + * change notification requests. + */ protected final void cleanupSession() { - - // Debug - - if (logger.isDebugEnabled() && hasDebug(DBG_STATE)) - logger.debug("Cleanup session, searches=" + getSearchCount() + ", treeConns=" + getConnectionCount() - + ", changeNotify=" + getNotifyChangeCount()); - - // Check if there are any active searches - - if (m_search != null) - { - - // Close all active searches - - for (int idx = 0; idx < m_search.length; idx++) - { - - // Check if the current search slot is active - - if (m_search[idx] != null) - deallocateSearchSlot(idx); - } - - // Release the search context list, clear the search count - - m_search = null; - m_searchCount = 0; - } - - // Check if there are open tree connections - - if (m_connections != null) - { - - synchronized (m_connections) - { - - // Close all active tree connections - - Enumeration enm = m_connections.elements(); - - while (enm.hasMoreElements()) - { - - // Get the current tree connection - - TreeConnection tree = enm.nextElement(); - DeviceInterface devIface = tree.getInterface(); - - // Check if there are open files on the share - - if (tree.openFileCount() > 0) - { - - // Close the open files, release locks - - for (int i = 0; i < tree.getFileTableLength(); i++) - { - - // Get an open file - - NetworkFile curFile = tree.findFile(i); - if (curFile != null && devIface instanceof DiskInterface) - { - - // Access the disk share interface - - DiskInterface diskIface = (DiskInterface) devIface; - - try - { - - // Remove the file from the tree connection list - - tree.removeFile(i, this); - - // Close the file - - diskIface.closeFile(this, tree, curFile); - } - catch (Exception ex) - { - } - } - } - } - // Inform the driver that the connection has been closed - - if (devIface != null) - devIface.treeClosed(this, tree); - } - - // Clear the tree connection list - - m_connections.clear(); - } - } + // Debug + + if (logger.isDebugEnabled() && hasDebug(DBG_STATE)) + logger.debug("Cleanup session, vcircuits=" + m_vcircuits.getCircuitCount() + ", changeNotify=" + getNotifyChangeCount()); + + // Close the virtual circuits + + if ( m_vcircuits.getCircuitCount() > 0) { + + // Enumerate the virtual circuits and close all circuits + + Enumeration uidEnum = m_vcircuits.enumerateUIDs(); + + while ( uidEnum.hasMoreElements()) { + + // Get the UID for the current circuit + + Integer uid = (Integer) uidEnum.nextElement(); + + // Close the virtual circuit + + VirtualCircuit vc = m_vcircuits.findCircuit( uid); + if ( vc != null) { + + // DEBUG + + if ( logger.isDebugEnabled() && hasDebug(DBG_STATE)) + logger.debug(" Cleanup vc=" + vc); + + vc.closeCircuit( this); + } + } + + // Clear the virtual circuit list + + m_vcircuits.clearCircuitList(); + } // Commit, or rollback, any active user transaction @@ -530,30 +437,6 @@ public class SMBSrvSession extends SrvSession implements Runnable } - /** - * Deallocate the specified search context/slot. - * - * @param ctxId int - */ - protected final void deallocateSearchSlot(int ctxId) - { - - // Check if the search array has been allocated and that the index is valid - - if (m_search == null || ctxId >= m_search.length) - return; - - // Close the search - - if (m_search[ctxId] != null) - m_search[ctxId].closeSearch(); - - // Free the specified search context slot - - m_searchCount--; - m_search[ctxId] = null; - } - /** * Finalize, object is about to be garbage collected. Make sure resources are released. */ @@ -569,25 +452,6 @@ public class SMBSrvSession extends SrvSession implements Runnable closeSocket(); } - /** - * Return the tree connection details for the specified tree id. - * - * @param treeId int - * @return TreeConnection - */ - protected final TreeConnection findConnection(int treeId) - { - - // Check if the tree id and connection array are valid - - if (m_connections == null) - return null; - - // Get the required tree connection details - - return (TreeConnection) m_connections.get(new Integer(treeId)); - } - /** * Return the input/output metwork buffer for this session. * @@ -598,16 +462,6 @@ public class SMBSrvSession extends SrvSession implements Runnable return m_buf; } - /** - * Return the count of active connections for this session. - * - * @return int - */ - public final int getConnectionCount() - { - return m_connections != null ? m_connections.size() : 0; - } - /** * Return the default flags SMB header value * @@ -763,35 +617,6 @@ public class SMBSrvSession extends SrvSession implements Runnable return m_pktHandler.getRemoteAddress(); } - /** - * Return the search context for the specified search id. - * - * @param srchId int - * @return SearchContext - */ - protected final SearchContext getSearchContext(int srchId) - { - - // Check if the search array is valid and the search index is valid - - if (m_search == null || srchId >= m_search.length) - return null; - - // Return the required search context - - return m_search[srchId]; - } - - /** - * Return the number of active tree searches. - * - * @return int - */ - public final int getSearchCount() - { - return m_searchCount; - } - /** * Return the server that this session is associated with. * @@ -812,26 +637,6 @@ public class SMBSrvSession extends SrvSession implements Runnable return getSMBServer().getServerName(); } - /** - * Determine if the session has a setup object - * - * @return boolean - */ - public final boolean hasSetupObject() - { - return m_setupObject != null; - } - - /** - * Return the session setup object - * - * @return Object - */ - public final Object getSetupObject() - { - return m_setupObject; - } - /** * Return the session state * @@ -871,10 +676,59 @@ public class SMBSrvSession extends SrvSession implements Runnable } /** - * Check if there is a change notification update pending - * - * @return boolean - */ + * Determine if the session has a setup object for the specified PID + * + * @param pid int + * @return boolean + */ + public final boolean hasSetupObject(int pid) { + if (m_setupObjects == null) + return false; + return m_setupObjects.get(new Integer(pid)) != null ? true : false; + } + + /** + * Return the session setup object for the specified PID + * + * @param pid int + * @return Object + */ + public final Object getSetupObject(int pid) { + if (m_setupObjects == null) + return null; + return m_setupObjects.get(new Integer(pid)); + } + + /** + * Store the setup object for the specified PID + * + * @param pid int + * @param obj Object + */ + public final void setSetupObject(int pid, Object obj) { + if (m_setupObjects == null) + m_setupObjects = new Hashtable(); + m_setupObjects.put(new Integer(pid), obj); + } + + /** + * Remove the session setup object for the specified PID + * + * @param pid + * int + * @return Object + */ + public final Object removeSetupObject(int pid) { + if (m_setupObjects == null) + return null; + return m_setupObjects.remove(new Integer(pid)); + } + + /** + * Check if there is a change notification update pending + * + * @return boolean + */ public final boolean hasNotifyPending() { return m_notifyPending; @@ -951,25 +805,6 @@ public class SMBSrvSession extends SrvSession implements Runnable m_buf = pkt.getBuffer(); } - /** - * Store the seach context in the specified slot. - * - * @param slot Slot to store the search context. - * @param srch SearchContext - */ - protected final void setSearchContext(int slot, SearchContext srch) - { - - // Check if the search slot id is valid - - if (m_search == null || slot > m_search.length) - return; - - // Store the context - - m_search[slot] = srch; - } - /** * Set the session state. * @@ -977,32 +812,16 @@ public class SMBSrvSession extends SrvSession implements Runnable */ protected void setState(int state) { - // Debug if (logger.isDebugEnabled() && hasDebug(DBG_STATE)) logger.debug("State changed to " + SMBSrvSessionState.getStateAsString(state)); - // Clear the setup object if the new state is the main session state - - if ( state == SMBSrvSessionState.SMBSESSION) - m_setupObject = null; - // Change the session state m_state = state; } - /** - * Set the setup object - * - * @param obj Object - */ - public final void setSetupObject( Object obj) - { - m_setupObject = obj; - } - /** * Process the NetBIOS session request message, either accept the session request and send back * a NetBIOS accept or reject the session and send back a NetBIOS reject and hangup the session. @@ -1444,45 +1263,6 @@ public class SMBSrvSession extends SrvSession implements Runnable getSMBServer().sessionOpened(this); } - /** - * Remove the specified tree connection from the active connection list. - * - * @param treeId int - */ - protected void removeConnection(int treeId) - { - - // Check if the tree id is valid - - if (m_connections == null) - return; - - // Close the connection and remove from the connection list - - synchronized (m_connections) - { - - // Get the connection - - Integer key = new Integer(treeId); - TreeConnection tree = (TreeConnection) m_connections.get(key); - - // Close the connection, release resources - - if (tree != null) - { - - // Close the connection - - tree.closeConnection(this); - - // Remove the connection from the connection list - - m_connections.remove(key); - } - } - } - /** * Start the SMB server session in a seperate thread. */ @@ -1780,10 +1560,20 @@ public class SMBSrvSession extends SrvSession implements Runnable if (m_smbPkt.isLongErrorCode()) { - - // Return the long/NT status code - - sendErrorResponseSMB(ntCode, SMBStatus.NTErr); + // Return the long/NT status code + + if ( ntCode != -1) { + + // Use the 32bit NT error code + + sendErrorResponseSMB( ntCode, SMBStatus.NTErr); + } + else { + + // Use the DOS error code + + sendErrorResponseSMB( stdCode, stdClass); + } } else { @@ -2035,34 +1825,4 @@ public class SMBSrvSession extends SrvSession implements Runnable if (req.getDiskContext() != null) req.getDiskContext().removeNotifyRequest(req); } - - /** - * Check if there is an active transaction - * - * @return boolean - */ - protected final boolean hasTransaction() - { - return m_transact != null ? true : false; - } - - /** - * Return the active transaction buffer - * - * @return TransactBuffer - */ - protected final SrvTransactBuffer getTransaction() - { - return m_transact; - } - - /** - * Set the active transaction buffer - * - * @param buf TransactBuffer - */ - protected final void setTransaction(SrvTransactBuffer buf) - { - m_transact = buf; - } } \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/smb/server/VirtualCircuit.java b/source/java/org/alfresco/filesys/smb/server/VirtualCircuit.java new file mode 100644 index 0000000000..b84555356d --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/VirtualCircuit.java @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.filesys.smb.server; + +import java.util.Enumeration; +import java.util.Hashtable; + +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.ClientInfo; +import org.alfresco.filesys.server.core.DeviceInterface; +import org.alfresco.filesys.server.core.SharedDevice; +import org.alfresco.filesys.server.filesys.DiskInterface; +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.filesys.server.filesys.SearchContext; +import org.alfresco.filesys.server.filesys.TooManyConnectionsException; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Virtual Circuit Class + * + *

+ * Represents an authenticated circuit on an SMB/CIFS session. There may be + * multiple virtual circuits opened on a single session/socket connection. + */ +public class VirtualCircuit { + + // Debug logging + + private static Log logger = LogFactory.getLog("org.alfresco.smb.protocol"); + + // Default and maximum number of connection slots + + public static final int DefaultConnections = 4; + public static final int MaxConnections = 16; + + // Tree ids are 16bit values + + private static final int TreeIdMask = 0x0000FFFF; + + // Default and maximum number of search slots + + private static final int DefaultSearches = 8; + private static final int MaxSearches = 256; + + // Invalid UID value + + public static final int InvalidUID = -1; + + // Virtual circuit UID value + // + // Allocated by the server and sent by the client to identify the virtual circuit + + private int m_uid = -1; + + // Virtual circuit number + + private int m_vcNum; + + // Client information for this virtual circuit + + private ClientInfo m_clientInfo; + + // Active tree connections + + private Hashtable m_connections; + + private int m_treeId; + + // List of active searches + + private SearchContext[] m_search; + + private int m_searchCount; + + // Active transaction details + + private SrvTransactBuffer m_transact; + + /** + * Class constructor + * + * @param vcNum + * int + * @param cInfo + * ClientInfo + */ + public VirtualCircuit(int vcNum, ClientInfo cInfo) { + m_vcNum = vcNum; + m_clientInfo = cInfo; + } + + /** + * Return the virtual circuit UID + * + * @return int + */ + public final int getUID() { + return m_uid; + } + + /** + * Return the virtual circuit number + * + * @return int + */ + public final int getVCNumber() { + return m_vcNum; + } + + /** + * Return the client information + * + * @return ClientInfo + */ + public final ClientInfo getClientInformation() { + return m_clientInfo; + } + + /** + * Add a new connection to this virtual circuit. Return the allocated tree + * id for the new connection. + * + * @param shrDev SharedDevice + * @return int Allocated tree id (connection id). + */ + public int addConnection(SharedDevice shrDev) + throws TooManyConnectionsException { + + // Check if the connection array has been allocated + + if (m_connections == null) + m_connections = new Hashtable(DefaultConnections); + + // Allocate an id for the tree connection + + int treeId = 0; + + synchronized (m_connections) { + + // Check if the tree connection table is full + + if (m_connections.size() == MaxConnections) + throw new TooManyConnectionsException(); + + // Find a free slot in the connection array + + treeId = (m_treeId++ & TreeIdMask); + Integer key = new Integer(treeId); + + while (m_connections.contains(key)) { + + // Try another tree id for the new connection + + treeId = (m_treeId++ & TreeIdMask); + key = new Integer(treeId); + } + + // Store the new tree connection + + m_connections.put(key, new TreeConnection(shrDev)); + } + + // Return the allocated tree id + + return treeId; + } + + /** + * Return the tree connection details for the specified tree id. + * + * @return com.starla.smbsrv.TreeConnection + * @param treeId + * int + */ + public final TreeConnection findConnection(int treeId) { + + // Check if the tree id and connection array are valid + + if (m_connections == null) + return null; + + // Get the required tree connection details + + return m_connections.get(new Integer(treeId)); + } + + /** + * Remove the specified tree connection from the active connection list. + * + * @param treeId + * int + * @param srvSession + * SrvSession + */ + protected void removeConnection(int treeId, SrvSession sess) { + + // Check if the tree id is valid + + if (m_connections == null) + return; + + // Close the connection and remove from the connection list + + synchronized (m_connections) { + + // Get the connection + + Integer key = new Integer(treeId); + TreeConnection tree = m_connections.get(key); + + // Close the connection, release resources + + if (tree != null) { + + // Close the connection + + tree.closeConnection(sess); + + // Remove the connection from the connection list + + m_connections.remove(key); + } + } + } + + /** + * Return the active tree connection count + * + * @return int + */ + public final int getConnectionCount() { + return m_connections != null ? m_connections.size() : 0; + } + + /** + * Allocate a slot in the active searches list for a new search. + * + * @return int Search slot index, or -1 if there are no more search slots + * available. + */ + public final int allocateSearchSlot() { + + // Check if the search array has been allocated + + if (m_search == null) + m_search = new SearchContext[DefaultSearches]; + + // Find a free slot for the new search + + int idx = 0; + + while (idx < m_search.length && m_search[idx] != null) + idx++; + + // Check if we found a free slot + + if (idx == m_search.length) { + + // The search array needs to be extended, check if we reached the + // limit. + + if (m_search.length >= MaxSearches) + return -1; + + // Extend the search array + + SearchContext[] newSearch = new SearchContext[m_search.length * 2]; + System.arraycopy(m_search, 0, newSearch, 0, m_search.length); + m_search = newSearch; + } + + // Return the allocated search slot index + + m_searchCount++; + return idx; + } + + /** + * Deallocate the specified search context/slot. + * + * @param ctxId + * int + */ + public final void deallocateSearchSlot(int ctxId) { + + // Check if the search array has been allocated and that the index is + // valid + + if (m_search == null || ctxId >= m_search.length) + return; + + // Close the search + + if (m_search[ctxId] != null) + m_search[ctxId].closeSearch(); + + // Free the specified search context slot + + m_searchCount--; + m_search[ctxId] = null; + } + + /** + * Return the search context for the specified search id. + * + * @return com.starla.smbsrv.SearchContext + * @param srchId + * int + */ + public final SearchContext getSearchContext(int srchId) { + + // Check if the search array is valid and the search index is valid + + if (m_search == null || srchId >= m_search.length) + return null; + + // Return the required search context + + return m_search[srchId]; + } + + /** + * Store the seach context in the specified slot. + * + * @param slot + * Slot to store the search context. + * @param srch + * com.starla.smbsrv.SearchContext + */ + public final void setSearchContext(int slot, SearchContext srch) { + + // Check if the search slot id is valid + + if (m_search == null || slot > m_search.length) + return; + + // Store the context + + m_search[slot] = srch; + } + + /** + * Return the number of active tree searches. + * + * @return int + */ + public final int getSearchCount() { + return m_searchCount; + } + + /** + * Check if there is an active transaction + * + * @return boolean + */ + public final boolean hasTransaction() { + return m_transact != null ? true : false; + } + + /** + * Return the active transaction buffer + * + * @return TransactBuffer + */ + public final SrvTransactBuffer getTransaction() { + return m_transact; + } + + /** + * Set the active transaction buffer + * + * @param buf + * TransactBuffer + */ + public final void setTransaction(SrvTransactBuffer buf) { + m_transact = buf; + } + + /** + * Set the UID for the circuit + * + * @param uid + * int + */ + public final void setUID(int uid) { + m_uid = uid; + } + + /** + * Close the virtual circuit, close active tree connections + * + * @param sess + * SrvSession + */ + public final void closeCircuit(SrvSession sess) { + + // Debug + + if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_STATE)) + logger.debug("Cleanup vc=" + getVCNumber() + ", UID=" + getUID() + ", searches=" + getSearchCount() + + ", treeConns=" + getConnectionCount()); + + // Check if there are any active searches + + if (m_search != null) { + + // Close all active searches + + for (int idx = 0; idx < m_search.length; idx++) { + + // Check if the current search slot is active + + if (m_search[idx] != null) + deallocateSearchSlot(idx); + } + + // Release the search context list, clear the search count + + m_search = null; + m_searchCount = 0; + } + + // Check if there are open tree connections + + if (m_connections != null) { + + synchronized (m_connections) { + + // Close all active tree connections + + Enumeration enm = m_connections.elements(); + + while (enm.hasMoreElements()) { + + // Get the current tree connection + + TreeConnection tree = (TreeConnection) enm.nextElement(); + DeviceInterface devIface = tree.getInterface(); + + // Check if there are open files on the share + + if (tree.openFileCount() > 0) { + + // Close the open files, release locks + + for (int i = 0; i < tree.getFileTableLength(); i++) { + + // Get an open file + + NetworkFile curFile = tree.findFile(i); + if (curFile != null && devIface instanceof DiskInterface) { + + // Access the disk share interface + + DiskInterface diskIface = (DiskInterface) devIface; + + try { + + // Remove the file from the tree connection list + + tree.removeFile(i, sess); + + // Close the file + + diskIface.closeFile(sess, tree, curFile); + } + catch (Exception ex) { + } + } + } + } + + // Inform the driver that the connection has been closed + + if (devIface != null) + devIface.treeClosed(sess, tree); + } + + // Clear the tree connection list + + m_connections.clear(); + } + } + } + + /** + * Return the virtual circuit details as a string + * + * @return String + */ + public String toString() { + StringBuffer str = new StringBuffer(); + + str.append("["); + str.append(getVCNumber()); + str.append(":"); + str.append(getUID()); + str.append(","); + str.append(getClientInformation()); + str.append(",Tree="); + str.append(getConnectionCount()); + str.append(",Searches="); + str.append(getSearchCount()); + str.append("]"); + + return str.toString(); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/smb/server/VirtualCircuitList.java b/source/java/org/alfresco/filesys/smb/server/VirtualCircuitList.java new file mode 100644 index 0000000000..f6042baf4b --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/server/VirtualCircuitList.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ + +package org.alfresco.filesys.smb.server; + +import java.util.Enumeration; +import java.util.Hashtable; + +import org.alfresco.filesys.server.SrvSession; + +/** + * Virtual Circuit List Class + * + *

+ * Contains a list of virtual circuits that belong to a session. + */ +public class VirtualCircuitList { + + // Default and maximum number of virtual circuits + + public static final int DefaultCircuits = 4; + public static final int MaxCircuits = 16; + + // UIDs are 16bit values + + private static final int UIDMask = 0x0000FFFF; + + // Active virtual circuits + + private Hashtable m_vcircuits; + + // Next available UID + + private int m_UID; + + /** + * Default constructor + */ + public VirtualCircuitList() { + + } + + /** + * Add a new virtual circuit to this session. Return the allocated UID for + * the new circuit. + * + * @param vcircuit + * VirtualCircuit + * @return int Allocated UID. + */ + public int addCircuit(VirtualCircuit vcircuit) { + + // Check if the circuit table has been allocated + + if (m_vcircuits == null) + m_vcircuits = new Hashtable(DefaultCircuits); + + // Allocate an id for the tree connection + + int uid = 0; + + synchronized (m_vcircuits) { + + // Check if the virtual circuit table is full + + if (m_vcircuits.size() == MaxCircuits) + return VirtualCircuit.InvalidUID; + + // Find a free slot in the circuit table + + uid = (m_UID++ & UIDMask); + Integer key = new Integer(uid); + + while (m_vcircuits.contains(key)) { + + // Try another user id for the new virtual circuit + + uid = (m_UID++ & UIDMask); + key = new Integer(uid); + } + + // Store the new virtual circuit + + vcircuit.setUID(uid); + m_vcircuits.put(key, vcircuit); + } + + // Return the allocated UID + + return uid; + } + + /** + * Return the virtual circuit details for the specified UID. + * + * @param uid + * int + * @return VirtualCircuit + */ + public final VirtualCircuit findCircuit(int uid) { + + // Check if the circuit table is valid + + if (m_vcircuits == null) + return null; + + // Get the required tree connection details + + return m_vcircuits.get(new Integer(uid)); + } + + /** + * Return the virtual circuit details for the specified UID. + * + * @param uid + * Integer + * @return VirtualCircuit + */ + public final VirtualCircuit findCircuit(Integer uid) { + + // Check if the circuit table is valid + + if (m_vcircuits == null) + return null; + + // Get the required tree connection details + + return m_vcircuits.get(uid); + } + + /** + * Enumerate the virtual circiuts + * + * @return Enumeration + */ + public final Enumeration enumerateUIDs() { + return m_vcircuits.keys(); + } + + /** + * Remove the specified virtual circuit from the active circuit list. + * + * @param uid int + * @param srvSession SrvSession + */ + public void removeCircuit(int uid, SrvSession sess) { + + // Check if the circuit table is valid + + if (m_vcircuits == null) + return; + + // Close the circuit and remove from the circuit table + + synchronized (m_vcircuits) { + + // Get the circuit + + Integer key = new Integer(uid); + VirtualCircuit vc = (VirtualCircuit) m_vcircuits.get(key); + + // Close the virtual circuit, release resources + + if (vc != null) { + + // Close the circuit + + vc.closeCircuit(sess); + + // Remove the circuit from the circuit table + + m_vcircuits.remove(key); + } + } + } + + /** + * Return the active tree connection count + * + * @return int + */ + public final int getCircuitCount() { + return m_vcircuits != null ? m_vcircuits.size() : 0; + } + + /** + * Clear the virtual circuit list + */ + public final void clearCircuitList() { + m_vcircuits.clear(); + } + + /** + * Return the virtual circuit list details as a string + * + * @return String + */ + public String toString() { + StringBuffer str = new StringBuffer(); + + str.append("[VCs="); + str.append(getCircuitCount()); + str.append("]"); + + return str.toString(); + } +}