diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 3bcc1068aa..dd062783d4 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -247,6 +247,15 @@ + + + + + + + + + @@ -278,7 +287,7 @@ - + @@ -328,6 +337,14 @@ + + + + + + + + diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index cc54b27fc2..4a6d51ff4d 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -260,7 +260,7 @@ name="org.alfresco.cache.ticketsCache" maxElementsInMemory="1000" eternal="true" - overflowToDisk="false" + overflowToDisk="true" /> \ No newline at end of file diff --git a/config/alfresco/extension/ehcache-custom.xml.sample.cluster b/config/alfresco/extension/ehcache-custom.xml.sample.cluster index 57d6a92ec4..c0a694f3f1 100644 --- a/config/alfresco/extension/ehcache-custom.xml.sample.cluster +++ b/config/alfresco/extension/ehcache-custom.xml.sample.cluster @@ -26,7 +26,7 @@ @@ -489,7 +489,7 @@ name="org.alfresco.cache.ticketsCache" maxElementsInMemory="1000" eternal="true" - overflowToDisk="false"> + overflowToDisk="true"> DefaultScheduler + + + false + @@ -48,6 +52,8 @@ + + diff --git a/source/java/org/alfresco/filesys/netbios/win32/Win32NetBIOS.java b/source/java/org/alfresco/filesys/netbios/win32/Win32NetBIOS.java index be12551e8f..45c1f5980d 100644 --- a/source/java/org/alfresco/filesys/netbios/win32/Win32NetBIOS.java +++ b/source/java/org/alfresco/filesys/netbios/win32/Win32NetBIOS.java @@ -529,7 +529,7 @@ public class Win32NetBIOS String ipAddr = niEnum.nextElement(); NetworkInterface ni = niList.get(ipAddr); - if (ni.getDisplayName().equalsIgnoreCase(name)) + if (ni.getName().equalsIgnoreCase(name)) { // Return the LANA for the network adapters TCP/IP address diff --git a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java index 312d2d3110..10b40b4895 100644 --- a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java @@ -25,6 +25,7 @@ package org.alfresco.filesys.server.auth; import java.security.NoSuchAlgorithmException; + import net.sf.acegisecurity.Authentication; import org.alfresco.filesys.server.SrvSession; @@ -32,6 +33,7 @@ import org.alfresco.filesys.server.auth.AuthContext; import org.alfresco.filesys.server.auth.CifsAuthenticator; import org.alfresco.filesys.server.auth.ClientInfo; import org.alfresco.filesys.server.auth.NTLanManAuthContext; +import org.alfresco.filesys.server.core.SharedDevice; import org.alfresco.filesys.smb.server.SMBSrvSession; import org.alfresco.filesys.util.HexDump; import org.alfresco.repo.security.authentication.NTLMMode; @@ -190,6 +192,25 @@ public class AlfrescoAuthenticator extends CifsAuthenticator return authSts; } + /** + * Authenticate a connection to a share. + * + * @param client User/client details from the tree connect request. + * @param share Shared device the client wants to connect to. + * @param pwd Share password. + * @param sess Server session. + * @return int Granted file permission level or disallow status if negative. See the + * FilePermission class. + */ + public int authenticateShareConnect(ClientInfo client, SharedDevice share, String sharePwd, SrvSession sess) + { + // Allow write access + // + // Main authentication is handled by authenticateUser() + + return CifsAuthenticator.Writeable; + } + /** * Return an authentication context for the new session * @@ -278,7 +299,9 @@ public class AlfrescoAuthenticator extends CifsAuthenticator // Generate the local encrypted password using the challenge that was sent to the client byte[] p21 = new byte[21]; - byte[] md4byts = m_md4Encoder.decodeHash(md4hash); + byte[] md4byts = null; + + md4byts = m_md4Encoder.decodeHash(md4hash); System.arraycopy(md4byts, 0, p21, 0, 16); // Get the challenge that was sent to the client @@ -321,6 +344,11 @@ public class AlfrescoAuthenticator extends CifsAuthenticator return CifsAuthenticator.AUTH_BADPASSWORD; } + // Logging + + if ( logger.isInfoEnabled()) + logger.info( "Logged on user " + client.getUserName() + " (" + sess.getRemoteAddress() + ") using auto-logon shared password"); + // Set the current user to be authenticated, save the authentication token client.setAuthenticationToken( m_authComponent.setCurrentUser(client.getUserName())); @@ -368,6 +396,10 @@ public class AlfrescoAuthenticator extends CifsAuthenticator sess.beginReadTransaction( m_transactionService); + // Default logon status to disallow + + int authSts = CifsAuthenticator.AUTH_DISALLOW; + // Get the authentication token for the session NTLMPassthruToken authToken = (NTLMPassthruToken) sess.getAuthenticationToken(); @@ -377,7 +409,6 @@ public class AlfrescoAuthenticator extends CifsAuthenticator // Get the appropriate hashed password for the algorithm - int authSts = CifsAuthenticator.AUTH_DISALLOW; byte[] hashedPassword = null; if ( alg == NTLM1) diff --git a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java index 0e43af00a8..4197d26d83 100644 --- a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java @@ -495,7 +495,7 @@ public abstract class CifsAuthenticator // Check if this is a null session logon - if (user.length() == 0 && domain.length() == 0 && uniPwdLen == 0 && ascPwdLen == 1) + if (user.length() == 0 && uniPwdLen == 0 && ascPwdLen <= 1) client.setLogonType(ClientInfo.LogonNull); // Authenticate the user diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/AuthSessionFactory.java b/source/java/org/alfresco/filesys/server/auth/passthru/AuthSessionFactory.java index a97dede76b..efacda7ec5 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/AuthSessionFactory.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/AuthSessionFactory.java @@ -136,10 +136,11 @@ public final class AuthSessionFactory * @param pkt SMBPacket to build the negotiate request * @param dlct SMB dialects to negotiate * @param pid Process id to be used by this new session + * @param extSec Enable/disable extended security negotiation * @return StringList */ - private final static StringList BuildNegotiatePacket(SMBPacket pkt, DialectSelector dlct, int pid) + private final static StringList BuildNegotiatePacket(SMBPacket pkt, DialectSelector dlct, int pid, boolean extSec) { // Initialize the SMB packet header fields @@ -147,14 +148,14 @@ public final class AuthSessionFactory pkt.setCommand(PacketType.Negotiate); pkt.setProcessId(pid); - // If the NT dialect is enabled set the Unicode flag in the request flags + // If the NT dialect is enabled set the Unicode flag int flags2 = 0; if (dlct.hasDialect(Dialect.NT)) flags2 += SMBPacket.FLG2_UNICODE; - if ( useExtendedSecurity()) + if ( useExtendedSecurity() && extSec == true) flags2 += SMBPacket.FLG2_EXTENDEDSECURITY; pkt.setFlags2(flags2); @@ -486,7 +487,7 @@ public final class AuthSessionFactory // Build the negotiate SMB dialect packet and exchange with the remote server - StringList diaList = BuildNegotiatePacket(pkt, selDialect, pid); + StringList diaList = BuildNegotiatePacket(pkt, selDialect, pid, shr.hasExtendedSecurityFlags()); pkt.ExchangeLowLevelSMB(netSession, pkt, true); // Determine the selected SMB dialect diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java b/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java index 3ca07748e5..2ee9618192 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java @@ -1016,7 +1016,7 @@ public class AuthenticateSession */ public final void doSessionSetup(String userName, byte[] ascPwd, byte[] uniPwd) throws IOException, SMBException { - doSessionSetup(null, userName, null, ascPwd, uniPwd); + doSessionSetup(null, userName, null, ascPwd, uniPwd, 0); } /** @@ -1029,7 +1029,7 @@ public class AuthenticateSession public final void doSessionSetup(Type3NTLMMessage type3Msg) throws IOException, SMBException { doSessionSetup(type3Msg.getDomain(), type3Msg.getUserName(), type3Msg.getWorkstation(), - type3Msg.getLMHash(), type3Msg.getNTLMHash()); + type3Msg.getLMHash(), type3Msg.getNTLMHash(), 0); } /** @@ -1040,11 +1040,13 @@ public class AuthenticateSession * @param wksName String * @param ascPwd ASCII password hash * @param uniPwd Unicode password hash + * @param vcNum Virtual circuit number * @exception IOException If a network error occurs * @exception SMBException If a CIFS error occurs */ public final void doSessionSetup(String domain, String userName, String wksName, - byte[] ascPwd, byte[] uniPwd) throws IOException, SMBException + byte[] ascPwd, byte[] uniPwd, int vcNum) + throws IOException, SMBException { // Check if we are using extended security @@ -1052,7 +1054,7 @@ public class AuthenticateSession { // Run the second phase of the extended security session setup - doExtendedSessionSetupPhase2(domain, userName, wksName, ascPwd, uniPwd); + doExtendedSessionSetupPhase2(domain, userName, wksName, ascPwd, uniPwd, vcNum); return; } @@ -1074,7 +1076,7 @@ public class AuthenticateSession pkt.setParameter(1, 0); // offset to next command pkt.setParameter(2, DefaultPacketSize); pkt.setParameter(3, 1); - pkt.setParameter(4, 0); // virtual circuit number + pkt.setParameter(4, vcNum); pkt.setParameterLong(5, 0); // session key // Set the share password length(s) @@ -1121,7 +1123,7 @@ public class AuthenticateSession pkt.packString("?", false); pkt.packString("Java VM", false); - pkt.packString("JLAN", false); + pkt.packString("Alfresco CIFS", false); // Set the packet length @@ -1175,7 +1177,7 @@ public class AuthenticateSession clbuf.append("Java VM"); clbuf.append((char) 0x00); - clbuf.append("JLAN"); + clbuf.append("Alfresco CIFS"); clbuf.append((char) 0x00); // Copy the remaining data to the SMB packet @@ -1275,7 +1277,6 @@ public class AuthenticateSession if (getDialect() == Dialect.NT) { - // Read the returned negotiate parameters, for NT dialect the parameters are not aligned m_pkt.resetParameterPointer(); @@ -1324,7 +1325,7 @@ public class AuthenticateSession // Set the default flags for subsequent SMB requests - defFlags2 = SMBPacket.FLG2_LONGFILENAMES + SMBPacket.FLG2_UNICODE + SMBPacket.FLG2_LONGERRORCODE; + defFlags2 = SMBPacket.FLG2_LONGFILENAMES + SMBPacket.FLG2_UNICODE + SMBPacket.FLG2_LONGERRORCODE + SMBPacket.FLG2_SECURITYSIG; if ( isUsingExtendedSecurity()) defFlags2 += SMBPacket.FLG2_EXTENDEDSECURITY; @@ -1485,7 +1486,7 @@ public class AuthenticateSession // Pack the OS details pkt.packString("Java VM", true); - pkt.packString("JLAN", true); + pkt.packString("Alfresco CIFS", true); pkt.packString("", true); @@ -1555,11 +1556,12 @@ public class AuthenticateSession * @param wksName String * @param lmPwd byte[] * @param ntlmPwd byte[] + * @param vcNum int * @exception IOException If a network error occurs * @eception SMBException If a CIFS error occurs */ private final void doExtendedSessionSetupPhase2(String domain, String userName, String wksName, - byte[] lmPwd, byte[] ntlmPwd) throws IOException, SMBException + byte[] lmPwd, byte[] ntlmPwd, int vcNum) throws IOException, SMBException { // Check if the domain name has been specified, if not then use the domain name from the // original connection details or the servers domain name @@ -1590,7 +1592,7 @@ public class AuthenticateSession pkt.setParameter(1, 0); // offset to next command pkt.setParameter(2, DefaultPacketSize); pkt.setParameter(3, 1); - pkt.setParameter(4, 0); // virtual circuit number + pkt.setParameter(4, vcNum); pkt.setParameterLong(5, 0); // session key // Clear the security blob length and reserved area @@ -1629,7 +1631,7 @@ public class AuthenticateSession // Pack the OS details pkt.packString("Java VM", true); - pkt.packString("JLAN", true); + pkt.packString("Alfresco CIFS", true); pkt.packString("", true); @@ -1645,4 +1647,5 @@ public class AuthenticateSession setGuest(pkt.getParameter(2) != 0 ? true : false); } + } diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java index 96730a9f51..b9638b4d4a 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java @@ -24,7 +24,10 @@ */ package org.alfresco.filesys.server.auth.passthru; +import java.util.ArrayList; import java.util.Hashtable; +import java.util.List; +import java.util.UUID; import javax.transaction.UserTransaction; @@ -33,14 +36,28 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.server.SessionListener; import org.alfresco.filesys.server.SrvSession; import org.alfresco.filesys.server.auth.AuthContext; +import org.alfresco.filesys.server.auth.AuthenticatorException; import org.alfresco.filesys.server.auth.ClientInfo; import org.alfresco.filesys.server.auth.CifsAuthenticator; import org.alfresco.filesys.server.auth.NTLanManAuthContext; +import org.alfresco.filesys.server.auth.ntlm.NTLM; +import org.alfresco.filesys.server.auth.ntlm.NTLMMessage; +import org.alfresco.filesys.server.auth.ntlm.TargetInfo; +import org.alfresco.filesys.server.auth.ntlm.Type1NTLMMessage; +import org.alfresco.filesys.server.auth.ntlm.Type2NTLMMessage; +import org.alfresco.filesys.server.auth.ntlm.Type3NTLMMessage; import org.alfresco.filesys.server.config.InvalidConfigurationException; import org.alfresco.filesys.server.config.ServerConfiguration; import org.alfresco.filesys.server.core.SharedDevice; +import org.alfresco.filesys.smb.Capability; +import org.alfresco.filesys.smb.SMBStatus; import org.alfresco.filesys.smb.server.SMBServer; +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.model.ContentModel; import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; @@ -66,6 +83,18 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL public final static int MinSessionTmo = 2000; // 2 seconds public final static int MaxSessionTmo = 30000; // 30 seconds + // Passthru keep alive interval + + public final static long PassthruKeepAliveInterval = 60000L; // 60 seconds + + // NTLM flags mask, used to mask out features that are not supported + + private static final int NTLM_FLAGS = NTLM.Flag56Bit + + NTLM.Flag128Bit + + NTLM.FlagLanManKey + + NTLM.FlagNegotiateNTLM + + NTLM.FlagNegotiateUnicode; + // Passthru servers used to authenticate users private PassthruServers m_passthruServers; @@ -192,7 +221,7 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL // using the session that has already been setup. AuthenticateSession authSess = passDetails.getAuthenticateSession(); - authSess.doSessionSetup(client.getDomain(), client.getUserName(), null, client.getANSIPassword(), client.getPassword()); + authSess.doSessionSetup(client.getDomain(), client.getUserName(), null, client.getANSIPassword(), client.getPassword(), 0); // Check if the user has been logged on as a guest @@ -311,51 +340,43 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL */ public AuthContext getAuthContext( SMBSrvSession sess) { + // Make sure the SMB server listener is installed - // Check for an SMB session + if ( m_server == null && sess instanceof SMBSrvSession) + { + SMBSrvSession smbSess = (SMBSrvSession) sess; + m_server = smbSess.getSMBServer(); + + // Install the server listener + + m_server.addSessionListener(this); + } + + // Open a connection to the authentication server, use normal session setup AuthContext authCtx = null; - - // Check if the client is already authenticated, and it is not a null logon - - if ( sess.hasAuthenticationContext() && sess.hasClientInformation() && - sess.getClientInformation().getAuthenticationToken() != null && - sess.getClientInformation().getLogonType() != ClientInfo.LogonNull) - { - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Re-using existing challenge, already authenticated"); - - // Return the previous challenge, user is already authenticated - - return sess.getAuthenticationContext(); - } - + try { - - // Open a connection to the authentication server - - AuthenticateSession authSess = m_passthruServers.openSession(); - if (authSess != null) - { - - // Create an entry in the active sessions table for the new session - - PassthruDetails passDetails = new PassthruDetails(sess, authSess); - m_sessions.put(sess.getUniqueId(), passDetails); - - // Use the challenge key returned from the authentication server - - authCtx = new NTLanManAuthContext( authSess.getEncryptionKey()); - sess.setAuthenticationContext( authCtx); - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Passthru sessId=" + authSess.getSessionId() + ", auth ctx=" + authCtx); - } + AuthenticateSession authSess = m_passthruServers.openSession(); + if (authSess != null) + { + + // Create an entry in the active sessions table for the new session + + PassthruDetails passDetails = new PassthruDetails(sess, authSess, false); + m_sessions.put(sess.getUniqueId(), passDetails); + + // Use the challenge key returned from the authentication server + + authCtx = new NTLanManAuthContext( authSess.getEncryptionKey()); + sess.setAuthenticationContext( authCtx); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Passthru sessId=" + authSess.getSessionId() + ", auth ctx=" + authCtx); + } } catch (Exception ex) { @@ -370,6 +391,685 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL return authCtx; } + /** + * Generate the CIFS negotiate response packet, the authenticator should add authentication specific fields + * to the response. + * + * @param sess SMBSrvSession + * @param respPkt SMBSrvPacket + * @param extendedSecurity boolean + * @exception AuthenticatorException + */ + public void generateNegotiateResponse(SMBSrvSession sess, SMBSrvPacket respPkt, boolean extendedSecurity) + throws AuthenticatorException + { + // If the client does not support extended security then return a standard negotiate response + // with an 8 byte challenge + + if ( extendedSecurity == false) + { + super.generateNegotiateResponse( sess, respPkt, extendedSecurity); + return; + } + + // Make sure the extended security negotiation flag is set + + if (( respPkt.getFlags2() & SMBSrvPacket.FLG2_EXTENDEDSECURITY) == 0) + respPkt.setFlags2( respPkt.getFlags2() + SMBSrvPacket.FLG2_EXTENDEDSECURITY); + + // Get the negotiate response byte area position + + int pos = respPkt.getByteOffset(); + byte[] buf = respPkt.getBuffer(); + + // Pack the CIFS server GUID into the negotiate response + + UUID serverGUID = sess.getSMBServer().getServerGUID(); + + DataPacker.putIntelLong( serverGUID.getLeastSignificantBits(), buf, pos); + DataPacker.putIntelLong( serverGUID.getMostSignificantBits(), buf, pos + 8); + + pos += 16; + + // Set the negotiate response length + + respPkt.setByteCount(pos - respPkt.getByteOffset()); + } + + /** + * Process the CIFS session setup request packet and build the session setup response + * + * @param sess SMBSrvSession + * @param reqPkt SMBSrvPacket + * @param respPkt SMBSrvPacket + * @exception SMBSrvException + */ + public void processSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt, SMBSrvPacket respPkt) + throws SMBSrvException + { + // Check that the received packet looks like a valid NT session setup andX request + + if (reqPkt.checkPacketIsValid(12, 0) == false) + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + + // Check if the request is using security blobs or the older hashed password format + + if ( reqPkt.getParameterCount() == 13) + { + // Process the standard password session setup + + super.processSessionSetup( 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); + + // Extract the client details from the session setup request + + int dataPos = reqPkt.getByteOffset(); + byte[] buf = reqPkt.getBuffer(); + + // Determine if ASCII or unicode strings are being used + + boolean isUni = reqPkt.isUnicode(); + + // Make a note of the security blob position + + int secBlobPos = dataPos; + + // Extract the clients primary domain name string + + dataPos += secBlobLen; + reqPkt.setPosition( dataPos); + + String domain = ""; + + if (reqPkt.hasMoreData()) { + + // Extract the callers domain name + + domain = reqPkt.unpackString(isUni); + + if (domain == null) + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + } + + // Extract the clients native operating system + + String clientOS = ""; + + if (reqPkt.hasMoreData()) { + + // Extract the callers operating system name + + clientOS = reqPkt.unpackString(isUni); + + if (clientOS == null) + throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + } + + // Store the client maximum buffer size, maximum multiplexed requests count and client capability flags + + sess.setClientMaximumBufferSize(maxBufSize != 0 ? maxBufSize : SMBSrvSession.DefaultBufferSize); + sess.setClientMaximumMultiplex(maxMpx); + sess.setClientCapabilities(capabs); + + // Create the client information and store in the session + + ClientInfo client = new ClientInfo(); + client.setDomain(domain); + client.setOperatingSystem(clientOS); + + client.setLogonType( ClientInfo.LogonNormal); + + // Set the remote address, if available + + if ( sess.hasRemoteAddress()) + client.setClientAddress(sess.getRemoteAddress().getHostAddress()); + + // Set the process id for this client, for multi-stage logons + + client.setProcessId( reqPkt.getProcessId()); + + // Get the current sesion setup object, or null + + Object setupObj = sess.getSetupObject( client.getProcessId()); + + // Process the security blob + + byte[] respBlob = null; + boolean isNTLMSSP = false; + + try + { + + // Check if the blob has the NTLMSSP signature + + if ( secBlobLen >= NTLM.Signature.length) { + + // Check for the NTLMSSP signature + + int idx = 0; + while ( idx < NTLM.Signature.length && buf[secBlobPos + idx] == NTLM.Signature[ idx]) + idx++; + + if ( idx == NTLM.Signature.length) + isNTLMSSP = true; + } + + // Process the security blob + + if ( isNTLMSSP == true) + { + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("NT Session setup NTLMSSP, MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); + + // Process an NTLMSSP security blob + + respBlob = doNtlmsspSessionSetup( sess, client, buf, secBlobPos, secBlobLen, isUni); + } + else + { + // Invalid blob type + + throw new SMBSrvException( SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + } + } + catch (SMBSrvException ex) + { + // Cleanup any stored context + + sess.removeSetupObject( client.getProcessId()); + + // Rethrow the exception + + throw ex; + } + + // Debug + + if ( logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("User " + client.getUserName() + " logged on " + (client != null ? " (type " + client.getLogonTypeString() + ")" : "")); + + // Update the client information if not already set + + if ( sess.getClientInformation() == null || + sess.getClientInformation().getUserName().length() == 0) { + + // Set the client details for the session + + sess.setClientInformation(client); + } + + // Get the response blob length, it can be null + + int respLen = respBlob != null ? respBlob.length : 0; + + // 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 + + boolean loggedOn = false; + + if ( 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( 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 + respPkt.setParameter(1, 0); // Offset to chained response + + respPkt.setParameter(2, 0); // Action + respPkt.setParameter(3, respLen); + } + else + { + // Build a completed session setup response + + respPkt.setLongErrorCode( SMBStatus.NTSuccess); + + // Build the session setup response SMB + + respPkt.setParameterCount(12); + respPkt.setParameter(0, 0xFF); // No chained response + respPkt.setParameter(1, 0); // Offset to chained response + + respPkt.setParameter(2, SMBSrvSession.DefaultBufferSize); + respPkt.setParameter(3, SMBSrvSession.NTMaxMultiplexed); + respPkt.setParameter(4, 0); // virtual circuit number + respPkt.setParameterLong(5, 0); // session key + respPkt.setParameter(7, respLen); + // 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 + + respPkt.setCommand( reqPkt.getCommand()); + respPkt.setByteCount(0); + + respPkt.setTreeId( 0); + respPkt.setUserId( uid); + + // Set the various flags + + int flags = respPkt.getFlags(); + flags &= ~SMBSrvPacket.FLG_CASELESS; + respPkt.setFlags(flags); + + int flags2 = SMBSrvPacket.FLG2_LONGFILENAMES + SMBSrvPacket.FLG2_EXTENDEDSECURITY + SMBSrvPacket.FLG2_LONGERRORCODE; + if ( isUni) + flags2 += SMBSrvPacket.FLG2_UNICODE; + respPkt.setFlags2( flags2); + + // Pack the security blob + + int pos = respPkt.getByteOffset(); + buf = respPkt.getBuffer(); + + if ( respBlob != null) + { + System.arraycopy( respBlob, 0, buf, pos, respBlob.length); + pos += respBlob.length; + } + + // Pack the OS, dialect and domain name strings + + if ( isUni) + pos = DataPacker.wordAlign(pos); + + pos = DataPacker.putString("Java", buf, pos, true, isUni); + pos = DataPacker.putString("Alfresco CIFS Server " + sess.getServer().isVersion(), buf, pos, true, isUni); + pos = DataPacker.putString(sess.getServer().getConfiguration().getDomainName(), buf, pos, true, isUni); + + respPkt.setByteCount(pos - respPkt.getByteOffset()); + } + + /** + * Process an NTLMSSP security blob + * + * @param sess SMBSrvSession + * @param client ClientInfo + * @param secbuf byte[] + * @param secpos int + * @param seclen int + * @param unicode boolean + * @exception SMBSrvException + */ + private final byte[] doNtlmsspSessionSetup( SMBSrvSession sess, ClientInfo client, + byte[] secbuf, int secpos, int seclen, boolean unicode) throws SMBSrvException + { + // Determine the NTLmSSP message type + + int msgType = NTLMMessage.isNTLMType( secbuf, secpos); + byte[] respBlob = null; + + if ( msgType == -1) + { + // DEBUG + + if ( logger.isDebugEnabled()) + { + logger.debug("Invalid NTLMSSP token received"); + logger.debug(" Token=" + HexDump.hexString( secbuf, secpos, seclen, " ")); + } + + // Return a logon failure status + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // Check for a type 1 NTLMSSP message + + else if ( msgType == NTLM.Type1) + { + // Create the type 1 NTLM message from the token + + Type1NTLMMessage type1Msg = new Type1NTLMMessage( secbuf, secpos, seclen); + + // Build the type 2 NTLM response message + // + // Get the flags from the client request and mask out unsupported features + + int ntlmFlags = type1Msg.getFlags() & NTLM_FLAGS; + + // Generate a challenge for the response + + NTLanManAuthContext ntlmCtx = (NTLanManAuthContext) getAuthContext( sess); + + // Build a type2 message to send back to the client, containing the challenge + + String domain = sess.getSMBServer().getServerName(); + + List tList = new ArrayList(); + + tList.add(new TargetInfo(NTLM.TargetDomain, domain)); + tList.add(new TargetInfo(NTLM.TargetServer, sess.getServerName())); + tList.add(new TargetInfo(NTLM.TargetDNSDomain, domain)); + tList.add(new TargetInfo(NTLM.TargetFullDNS, domain)); + + ntlmFlags = NTLM.FlagChallengeAccept + NTLM.FlagRequestTarget + + NTLM.FlagNegotiateNTLM + NTLM.FlagNegotiateUnicode + + NTLM.FlagKeyExchange + NTLM.FlagTargetInfo + NTLM.Flag56Bit; + + // NTLM.FlagAlwaysSign + NTLM.FlagNegotiateSign + + + Type2NTLMMessage type2Msg = new Type2NTLMMessage(); + + type2Msg.buildType2(ntlmFlags, domain, ntlmCtx.getChallenge(), null, tList); + + // Store the type 2 message in the session until the session setup is complete + + sess.setSetupObject( client.getProcessId(), type2Msg); + + // Set the response blob using the type 2 message + + respBlob = type2Msg.getBytes(); + } + else if ( msgType == NTLM.Type3) + { + // Create the type 3 NTLM message from the token + + Type3NTLMMessage type3Msg = new Type3NTLMMessage( secbuf, secpos, seclen, unicode); + + // Make sure a type 2 message was stored in the first stage of the session setup + + if ( sess.hasSetupObject( client.getProcessId()) == false || sess.getSetupObject( client.getProcessId()) instanceof Type2NTLMMessage == false) + { + // Clear the setup object + + sess.removeSetupObject( client.getProcessId()); + + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // Determine if the client sent us NTLMv1 or NTLMv2 + + if ( type3Msg.hasFlag( NTLM.Flag128Bit) && type3Msg.hasFlag( NTLM.FlagNTLM2Key)) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Received NTLMSSP/NTLMv2, not supported"); + + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + else + { + // Looks like an NTLMv1 blob + + doNTLMv1Logon( sess, client, type3Msg); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Logged on using NTLMSSP/NTLMv1"); + } + } + + // Return the response blob + + return respBlob; + } + + /** + * Perform an NTLMv1 logon using the NTLMSSP type3 message + * + * @param sess SMBSrvSession + * @param client ClientInfo + * @param type3Msg Type3NTLMMessage + * @exception SMBSrvException + */ + private final void doNTLMv1Logon(SMBSrvSession sess, ClientInfo client, Type3NTLMMessage type3Msg) + throws SMBSrvException + { + // Get the type 2 message that contains the challenge sent to the client + + Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject( client.getProcessId()); + sess.removeSetupObject( client.getProcessId()); + + // Get the NTLM logon details + + String userName = type3Msg.getUserName(); + + // Check for a null logon + + if ( userName.length() == 0) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Null logon"); + + // Indicate a null logon in the client information + + client.setLogonType( ClientInfo.LogonNull); + return; + } + + // Find the active authentication session details for the server session + + PassthruDetails passDetails = m_sessions.get(sess.getUniqueId()); + + if (passDetails != null) + { + try + { + // Authenticate the user by passing the hashed password to the authentication server + // using the session that has already been setup. + + AuthenticateSession authSess = passDetails.getAuthenticateSession(); + authSess.doSessionSetup( type3Msg.getDomain(), userName, null, type3Msg.getLMHash(), type3Msg.getNTLMHash(), 0); + + // Check if the user has been logged on as a guest + + if (authSess.isGuest()) + { + // Check if the local server allows guest access + + if (allowGuest() == true) + { + // Get a guest authentication token + + doGuestLogon( client, sess); + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("Passthru authenticate user=" + userName + ", GUEST"); + } + } + else + { + // Wrap the service calls in a transaction + + UserTransaction tx = m_transactionService.getUserTransaction( false); + + try + { + // Start the transaction + + tx.begin(); + + // Map the passthru username to an Alfresco person + + NodeRef userNode = m_personService.getPerson(userName); + if ( userNode != null) + { + // Get the person name and use that as the current user to line up with permission checks + + String personName = (String) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME); + m_authComponent.setCurrentUser(personName); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Setting current user using person " + personName + " (username " + userName + ")"); + } + else + { + // Set using the user name + + m_authComponent.setCurrentUser( userName); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Setting current user using username " + userName); + } + + // Get the authentication token and store + + client.setAuthenticationToken( m_authComponent.getCurrentAuthentication()); + + // Indicate that the client is logged on + + client.setLogonType( ClientInfo.LogonNormal); + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("Passthru authenticate user=" + userName + ", FULL"); + } + finally + { + // Commit the transaction + + if ( tx != null) + { + try { + tx.commit(); + } + catch (Exception ex) + { + // Sink it + } + } + } + } + + // Update the client details + + client.setDomain( type3Msg.getDomain()); + client.setUserName( userName); + } + catch (Exception ex) + { + + // Debug + + logger.error(ex.getMessage()); + + // Indicate logon failure + + throw new SMBSrvException( SMBStatus.NTErr, SMBStatus.NTLogonFailure); + } + finally + { + // Remove the passthru session from the active list + + m_sessions.remove(sess.getUniqueId()); + + // Close the passthru authentication session + + try + { + + // Close the authentication session + + AuthenticateSession authSess = passDetails.getAuthenticateSession(); + authSess.CloseSession(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Closed auth session, sessId=" + authSess.getSessionId()); + } + catch (Exception ex) + { + + // Debug + + logger.error("Passthru error closing session (auth user)", ex); + } + } + } + else + { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug(" No PassthruDetails for " + sess.getUniqueId()); + + // Indicate logon failure + + throw new SMBSrvException( SMBStatus.NTErr, SMBStatus.NTLogonFailure); + } + } + /** * Initialzie the authenticator * @@ -510,6 +1210,18 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL smbServer.addSessionListener(this); } + /** + * Return the server capability flags + * + * @return int + */ + public int getServerCapabilities() + { + return Capability.Unicode + Capability.RemoteAPIs + Capability.NTSMBs + Capability.NTFind + + Capability.NTStatus + Capability.LargeFiles + Capability.LargeRead + Capability.LargeWrite + + Capability.ExtendedSecurity; + } + /** * Close the authenticator, perform cleanup */ @@ -520,7 +1232,7 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL if ( m_passthruServers != null) m_passthruServers.shutdown(); } - + /** * SMB server session closed notification * @@ -582,48 +1294,37 @@ public class PassthruAuthenticator extends CifsAuthenticator implements SessionL */ public void sessionLoggedOn(SrvSession sess) { + // Check if there is an active session to the authentication server for this local + // session - // Check if the client information has an empty user name, if so then do not close the - // authentication session + PassthruDetails passDetails = m_sessions.get(sess.getUniqueId()); - if (sess.hasClientInformation() && sess.getClientInformation().getUserName() != null - && sess.getClientInformation().getUserName().length() > 0) + if (passDetails != null && passDetails.hasKeepAlive() == false) { + // Remove the passthru session from the active list - // Check if there is an active session to the authentication server for this local - // session + m_sessions.remove(sess.getUniqueId()); - PassthruDetails passDetails = m_sessions.get(sess.getUniqueId()); + // Close the passthru authentication session - if (passDetails != null) + try + { + // Close the authentication session + + AuthenticateSession authSess = passDetails.getAuthenticateSession(); + authSess.CloseSession(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Closed auth session, sessId=" + authSess.getSessionId()); + } + catch (Exception ex) { - // Remove the passthru session from the active list + // Debug - m_sessions.remove(sess.getUniqueId()); - - // Close the passthru authentication session - - try - { - - // Close the authentication session - - AuthenticateSession authSess = passDetails.getAuthenticateSession(); - authSess.CloseSession(); - - // DEBUG - - if (logger.isDebugEnabled()) - logger.debug("Closed auth session, sessId=" + authSess.getSessionId()); - } - catch (Exception ex) - { - - // Debug - - logger.error("Passthru error closing session (logon)", ex); - } + logger.error("Passthru error closing session (logon)", ex); } } } diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruDetails.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruDetails.java index 9d2d174cd0..1fc9b5a6a2 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruDetails.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruDetails.java @@ -34,7 +34,6 @@ import org.alfresco.filesys.server.SrvSession; */ class PassthruDetails { - // Server session private SrvSession m_sess; @@ -43,6 +42,10 @@ class PassthruDetails private AuthenticateSession m_authSess; + // Flag to indicate if session should be kept alive + + private boolean m_keepAlive; + /** * Class constructor * @@ -55,6 +58,21 @@ class PassthruDetails m_authSess = authSess; } + /** + * Class constructor + * + * @param sess SrvSession + * @param authSess AuthenticateSession + * @param keepAlive boolean + */ + public PassthruDetails(SrvSession sess, AuthenticateSession authSess, boolean keepAlive) + { + m_sess = sess; + m_authSess = authSess; + + m_keepAlive = keepAlive; + } + /** * Return the session details * @@ -74,4 +92,14 @@ class PassthruDetails { return m_authSess; } + + /** + * Check if the authentication session should be kept alive + * + * @return boolean + */ + public final boolean hasKeepAlive() + { + return m_keepAlive; + } } diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java index 4b52a157d7..423d9be333 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java @@ -306,16 +306,16 @@ public class PassthruServers */ public final AuthenticateSession openSession() { - return openSession( 0); + return openSession( false); } /** * Open a new session to an authentication server * - * @param extFlags int + * @param useExtSec boolean * @return AuthenticateSession */ - public final AuthenticateSession openSession(int extFlags) + public final AuthenticateSession openSession(boolean useExtSec) { // Get the details of an authentication server to connect to @@ -331,7 +331,8 @@ public class PassthruServers // Open a new authentication session to the server PCShare authShare = new PCShare(passthruServer.getAddress().getHostAddress(), "IPC$", "", ""); - authShare.setExtendedSecurityFlags( extFlags); + if ( useExtSec == true) + authShare.setExtendedSecurityFlags( SMBPacket.FLG2_EXTENDEDSECURITY); AuthenticateSession authSess = null; diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/SMBPacket.java b/source/java/org/alfresco/filesys/server/auth/passthru/SMBPacket.java index 491be0949e..df20e6f664 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/SMBPacket.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/SMBPacket.java @@ -88,6 +88,7 @@ public class SMBPacket public static final int FLG2_LONGFILENAMES = 0x0001; public static final int FLG2_EXTENDEDATTRIB = 0x0002; + public static final int FLG2_SECURITYSIG = 0x0004; public static final int FLG2_EXTENDEDSECURITY = 0x0800; public static final int FLG2_READIFEXE = 0x2000; public static final int FLG2_LONGERRORCODE = 0x4000; @@ -589,6 +590,16 @@ public class SMBPacket } /** + * Get the SMB signing value, as a long value + * + * @return long + */ + public final long getSignature() + { + return DataPacker.getIntelLong( m_smbbuf, SIGNATURE); + } + + /** * Get the tree identifier (TID) * * @return Tree identifier (TID) @@ -1131,6 +1142,16 @@ public class SMBPacket } /** + * Set a long error code (NT status code) + * + * @param sts int + */ + public final void setLongErrorCode(int lsts) + { + DataPacker.putIntelInt(lsts, m_smbbuf, ERRORCODE); + } + + /** * Set the SMB flags value. * * @param flg SMB flags value. @@ -1225,6 +1246,38 @@ public class SMBPacket DataPacker.putIntelShort(sid, m_smbbuf, SID); } + /** + * Set the SMB signing signature + * + * @param ival int + */ + public final void setSignature( int ival) + { + DataPacker.putIntelInt( ival, m_smbbuf, SIGNATURE); + DataPacker.putZeros( m_smbbuf, SIGNATURE + 4, 4); + } + + /** + * Set the SMB signing signature + * + * @param lval long + */ + public final void setSignature( long lval) + { + DataPacker.putIntelLong( lval, m_smbbuf, SIGNATURE); + } + + /** + * Set the SMB signing signature + * + * @param byts byte[] + * @param offset int + */ + public final void setSignature( byte[] byts, int offset) + { + System.arraycopy( byts, offset, m_smbbuf, SIGNATURE, 8); + } + /** * Set the tree identifier (TID) * diff --git a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java index 4221730ee1..b905c1b867 100644 --- a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java +++ b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java @@ -1254,20 +1254,39 @@ public class ServerConfiguration extends AbstractLifecycleBean String lanaStr = elem.getAttribute("lana"); if (lanaStr != null && lanaStr.length() > 0) { - - // Validate the LANA number - + // Check if the LANA has been specified as an IP address or adapter name + int lana = -1; - - try + + if ( IPAddress.isNumericAddress( lanaStr)) { - lana = Integer.parseInt(lanaStr); - } - catch (NumberFormatException ex) - { - throw new AlfrescoRuntimeException("Invalid win32 NetBIOS LANA specified"); + + // Convert the IP address to a LANA id + + lana = Win32NetBIOS.getLANAForIPAddress( lanaStr); + if ( lana == -1) + throw new AlfrescoRuntimeException( "Failed to convert IP address " + lanaStr + " to a LANA"); } + else if ( lanaStr.length() > 1 && Character.isLetter( lanaStr.charAt( 0))) { + // Convert the network adapter to a LANA id + + lana = Win32NetBIOS.getLANAForAdapterName( lanaStr); + if ( lana == -1) + throw new AlfrescoRuntimeException( "Failed to convert network adapter " + lanaStr + " to a LANA"); + } + else { + + try + { + lana = Integer.parseInt(lanaStr); + } + catch (NumberFormatException ex) + { + throw new AlfrescoRuntimeException("Invalid win32 NetBIOS LANA specified"); + } + } + // LANA should be in the range 0-255 if (lana < 0 || lana > 255) @@ -2265,8 +2284,8 @@ public class ServerConfiguration extends AbstractLifecycleBean { // Check if the appropriate authentication component type is configured - if ( ntlmMode != NTLMMode.NONE) - throw new AlfrescoRuntimeException("Wrong authentication setup for passthru authenticator (can only be used with LDAP/JAAS auth component)"); + if ( ntlmMode == NTLMMode.MD4_PROVIDER) + throw new AlfrescoRuntimeException("Wrong authentication setup for passthru authenticator (cannot be used with Alfresco users)"); // Load the passthru authenticator dynamically diff --git a/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java b/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java index a8b5d942ac..ae319b9c0b 100644 --- a/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java +++ b/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java @@ -3153,6 +3153,17 @@ public class NTProtocolHandler extends CoreProtocolHandler return; } + // Check if the search is for an NTFS stream + + if ( FileName.containsStreamName( srchPath)) + { + // NTFS streams not supported + + m_sess.sendErrorResponseSMB(SMBStatus.NTObjectNotFound, SMBStatus.SRVNonSpecificError, + SMBStatus.ErrSrv); + return; + } + // Access the shared device disk interface SearchContext ctx = null; diff --git a/source/java/org/alfresco/filesys/smb/server/SMBSrvSession.java b/source/java/org/alfresco/filesys/smb/server/SMBSrvSession.java index 240edc84cf..60428451aa 100644 --- a/source/java/org/alfresco/filesys/smb/server/SMBSrvSession.java +++ b/source/java/org/alfresco/filesys/smb/server/SMBSrvSession.java @@ -313,6 +313,16 @@ public class SMBSrvSession extends SrvSession implements Runnable m_vcircuits.removeCircuit(uid, this); } + /** + * Return the count of virtual circuits on this session + * + * @return int + */ + public final int getVirtualCircuitCount() + { + return m_vcircuits != null ? m_vcircuits.getCircuitCount() : 0; + } + /** * Cleanup any resources owned by this session, close files, searches and * change notification requests. diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index ad5e08e2ec..13deb28c83 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -46,7 +46,9 @@ import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; @@ -298,6 +300,24 @@ public class Node implements Serializable, Scopeable { if (name != null) { + QName typeQName = getType(); + if ((services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_FOLDER) && + !services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_SYSTEM_FOLDER)) || + services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_CONTENT)) + { + try + { + this.services.getFileFolderService().rename(this.nodeRef, name); + } + catch (FileExistsException e) + { + throw new AlfrescoRuntimeException("Failed to rename node " + nodeRef + " to " + name, e); + } + catch (FileNotFoundException e) + { + throw new AlfrescoRuntimeException("Failed to rename node " + nodeRef + " to " + name, e); + } + } this.getProperties().put(ContentModel.PROP_NAME.toString(), name.toString()); } } diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java index 89661151ee..bccee2bac1 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java @@ -35,6 +35,8 @@ import javax.transaction.UserTransaction; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.domain.ChildAssoc; +import org.alfresco.repo.domain.Node; import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; @@ -353,4 +355,29 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest mlTextProperty, propertiesDirect.get(BaseNodeServiceTest.PROP_QNAME_ML_TEXT_VALUE)); } + + public void testDuplicatePrimaryParentHandling() throws Exception + { + Map assocRefs = buildNodeGraph(); + // get the node to play with + ChildAssociationRef n1pn3Ref = assocRefs.get(QName.createQName(BaseNodeServiceTest.NAMESPACE, "n1_p_n3")); + ChildAssociationRef n6pn8Ref = assocRefs.get(QName.createQName(BaseNodeServiceTest.NAMESPACE, "n6_p_n8")); + final NodeRef n1Ref = n1pn3Ref.getParentRef(); + final NodeRef n8Ref = n6pn8Ref.getChildRef(); + + // Add a make n1 a second primary parent of n8 + Node n1 = nodeDaoService.getNode(n1Ref); + Node n8 = nodeDaoService.getNode(n8Ref); + ChildAssoc assoc = nodeDaoService.newChildAssoc( + n1, + n8, + true, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NAMESPACE, "n1pn8")); + + // Now get the node primary parent + nodeService.getPrimaryParent(n8Ref); + // Get it again + nodeService.getPrimaryParent(n8Ref); + } } diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index cd0f89cfa7..65a2347c54 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -900,6 +900,15 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements getSession().flush(); } + private Set warnedDuplicateParents = new HashSet(3); + /** + * @inheritDoc + * + * This method includes a check for multiple primary parent associations. + * The check doesn't fail but will warn (once per instance) of the occurence of + * the error. It is up to the administrator to fix the issue at the moment, but + * the server will not stop working. + */ public ChildAssoc getPrimaryParentAssoc(Node node) { // get the assocs pointing to the node @@ -914,12 +923,20 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } else if (primaryAssoc != null) { - // we have more than one somehow - throw new DataIntegrityViolationException( - "Multiple primary associations: \n" + - " child: " + node + "\n" + - " first primary assoc: " + primaryAssoc + "\n" + - " second primary assoc: " + assoc); + // We have found one already. + synchronized(warnedDuplicateParents) + { + NodeRef childNodeRef = node.getNodeRef(); + boolean added = warnedDuplicateParents.add(childNodeRef); + if (added) + { + logger.warn( + "Multiple primary associations: \n" + + " first primary assoc: " + primaryAssoc + "\n" + + " second primary assoc: " + assoc + "\n" + + "When running in a cluster, check that the caches are properly shared."); + } + } } primaryAssoc = assoc; // we keep looping to hunt out data integrity issues @@ -938,9 +955,21 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } if (!rootNode.equals(node)) { - // it wasn't the root node - throw new DataIntegrityViolationException("Non-root node has no primary parent: \n" + - " child: " + node); + // Reload the node to ensure that it is properly initialized + getSession().refresh(node); + // Check if it has any parents yet. + if (node.getParentAssocs().size() == 0) + { + // It wasn't the root node and definitely has no parent + throw new DataIntegrityViolationException( + "Non-root node has no primary parent: \n" + + " child: " + node); + } + else + { + // Repeat this method with confidence + primaryAssoc = getPrimaryParentAssoc(node); + } } } // done diff --git a/source/java/org/alfresco/repo/node/index/IndexRecoveryBootstrapBean.java b/source/java/org/alfresco/repo/node/index/IndexRecoveryBootstrapBean.java new file mode 100644 index 0000000000..2b4dc16904 --- /dev/null +++ b/source/java/org/alfresco/repo/node/index/IndexRecoveryBootstrapBean.java @@ -0,0 +1,54 @@ +/* + * 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.repo.node.index; + +import org.alfresco.util.AbstractLifecycleBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; + +public class IndexRecoveryBootstrapBean extends AbstractLifecycleBean +{ + protected final static Log log = LogFactory.getLog(IndexRecoveryBootstrapBean.class); + + IndexRecovery indexRecoveryComponent; + + @Override + protected void onBootstrap(ApplicationEvent event) + { + // reindex + log.info("Checking/Recovering indexes ..."); + indexRecoveryComponent.reindex(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + // Nothing to do + } + + public IndexRecovery getIndexRecoveryComponent() + { + return indexRecoveryComponent; + } + + public void setIndexRecoveryComponent(IndexRecovery indexRecoveryComponent) + { + this.indexRecoveryComponent = indexRecoveryComponent; + } + +}