Java tutorial
/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.filesys.auth.cifs; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.util.ArrayList; import java.util.List; import java.util.Vector; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import javax.security.sasl.RealmCallback; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.alfresco.AlfrescoClientInfo; import org.alfresco.jlan.server.auth.AuthenticatorException; import org.alfresco.jlan.server.auth.ClientInfo; import org.alfresco.jlan.server.auth.NTLanManAuthContext; import org.alfresco.jlan.server.auth.kerberos.KerberosApReq; import org.alfresco.jlan.server.auth.kerberos.KerberosDetails; import org.alfresco.jlan.server.auth.kerberos.KrbAuthContext; import org.alfresco.jlan.server.auth.kerberos.SessionSetupPrivilegedAction; import org.alfresco.jlan.server.auth.ntlm.NTLM; import org.alfresco.jlan.server.auth.ntlm.NTLMMessage; import org.alfresco.jlan.server.auth.ntlm.NTLMv2Blob; import org.alfresco.jlan.server.auth.ntlm.TargetInfo; import org.alfresco.jlan.server.auth.ntlm.Type1NTLMMessage; import org.alfresco.jlan.server.auth.ntlm.Type2NTLMMessage; import org.alfresco.jlan.server.auth.ntlm.Type3NTLMMessage; import org.alfresco.jlan.server.auth.spnego.NegTokenInit; import org.alfresco.jlan.server.auth.spnego.NegTokenTarg; import org.alfresco.jlan.server.auth.spnego.OID; import org.alfresco.jlan.server.auth.spnego.SPNEGO; import org.alfresco.jlan.server.config.InvalidConfigurationException; import org.alfresco.jlan.server.config.ServerConfiguration; import org.alfresco.jlan.server.core.NoPooledMemoryException; import org.alfresco.jlan.smb.Capability; import org.alfresco.jlan.smb.SMBStatus; import org.alfresco.jlan.smb.dcerpc.UUID; import org.alfresco.jlan.smb.server.SMBSrvException; import org.alfresco.jlan.smb.server.SMBSrvPacket; import org.alfresco.jlan.smb.server.SMBSrvSession; import org.alfresco.jlan.smb.server.VirtualCircuit; import org.alfresco.jlan.util.DataPacker; import org.alfresco.jlan.util.HexDump; import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.NTLMMode; import org.alfresco.repo.security.authentication.ntlm.NLTMAuthenticator; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ietf.jgss.Oid; import org.springframework.extensions.config.ConfigElement; /** * Enterprise CIFS Authenticator Class * * <p>CIFS authenticator that supports NTLMSSP and Kerberos logins. * * @author gkspencer */ public class EnterpriseCifsAuthenticator extends CifsAuthenticatorBase implements CallbackHandler { // Constants // // Default login configuration entry name protected static final Log logger = LogFactory.getLog(EnterpriseCifsAuthenticator.class); // logger is defined in base class. private static final String LoginConfigEntry = "AlfrescoCIFS"; // 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.FlagNTLM2Key + NTLM.FlagNegotiateUnicode; // Use NTLMSSP or SPNEGO private boolean m_useRawNTLMSSP = true; // Flag to control whether NTLMv1 is accepted private boolean m_acceptNTLMv1 = true; // Should we strip the @domain suffix from the Kerberos username? private boolean m_stripKerberosUsernameSuffix = true; // Kerberos settings // // Account name and password for server ticket // // The account name must be built from the CIFS server name, in the format :- // // cifs/<server_name>@<realm> private String m_accountName; private String m_password; // Kerberos realm private String m_krbRealm; // Login configuration entry name private String m_loginEntryName = LoginConfigEntry; // Server login context private LoginContext m_loginContext; // SPNEGO NegTokenInit blob, sent to the client in the SMB negotiate response private Vector<Oid> m_mechTypes; private byte[] m_negTokenInit; private String m_mecListMIC; // Enable Kerberos debug output private boolean kerberosDebug; // Disable NTLM logons, only Kerberos logons allowed private boolean disableNTLM; // Enable ticket cracking code, required for Java5 JVMs private boolean m_enableTicketCracking; /** * Class constructor */ public EnterpriseCifsAuthenticator() { setExtendedSecurity(true); } /** * Sets the HTTP service account password. (the Principal should be configured in java.login.config) * * @param password * the password to set */ public void setPassword(String password) { this.m_password = password; } /** * Sets the HTTP service account realm. * * @param realm * the realm to set */ public void setRealm(String realm) { m_krbRealm = realm; } /** * Sets the HTTP service login configuration entry name. * * @param jaasConfigEntryName * the loginEntryName to set */ public void setJaasConfigEntryName(String jaasConfigEntryName) { m_loginEntryName = jaasConfigEntryName; } public void setKerberosDebug(boolean kerberosDebug) { this.kerberosDebug = kerberosDebug; } public void setDisableNTLM(boolean disableNTLM) { this.disableNTLM = disableNTLM; } public void setUseSPNEGO(boolean useSPNEGO) { m_useRawNTLMSSP = !useSPNEGO; } public void setDisallowNTLMv1(boolean disallowNTLMv1) { this.m_acceptNTLMv1 = !disallowNTLMv1; } public void setStripKerberosUsernameSuffix(boolean stripKerberosUsernameSuffix) { m_stripKerberosUsernameSuffix = stripKerberosUsernameSuffix; } /** * Enable Kerbeors ticket cracking code that is required for Java5 * * @param enaTktCracking boolean */ public void setEnableTicketCracking(boolean enaTktCracking) { m_enableTicketCracking = enaTktCracking; // Debug if (logger.isInfoEnabled() && enaTktCracking) { logger.info("CIFS Kerberos authentication, ticket cracking enabled (for mutual authentication)"); } } /** * Initialize the authenticator (via the config service) * * @param config ServerConfiguration * @param params ConfigElement * @exception InvalidConfigurationException */ public void initialize(ServerConfiguration config, ConfigElement params) throws InvalidConfigurationException { // Check if Java API Kerberos debug output should be enabled setKerberosDebug(params.getChild("kerberosDebug") != null); // Check if Kerberos is enabled, get the Kerberos realm ConfigElement krbRealm = params.getChild("Realm"); if (krbRealm != null && krbRealm.getValue() != null && krbRealm.getValue().length() > 0) { setRealm(krbRealm.getValue()); // Get the CIFS service account password ConfigElement srvPassword = params.getChild("Password"); if (srvPassword != null && srvPassword.getValue() != null && srvPassword.getValue().length() > 0) { setPassword(srvPassword.getValue()); } else { throw new InvalidConfigurationException("CIFS service account password not specified"); } // Get the login configuration entry name ConfigElement loginEntry = params.getChild("LoginEntry"); if (loginEntry != null) { if (loginEntry.getValue() != null && loginEntry.getValue().length() > 0) { // Set the login configuration entry name to use setJaasConfigEntryName(loginEntry.getValue()); } else { throw new InvalidConfigurationException("Invalid login entry specified"); } } setDisableNTLM(params.getChild("disableNTLM") != null); // Indicate that SPNEGO security blobs are being used setUseSPNEGO(true); } else { // Check if raw NTLMSSP or SPNEGO/NTLMSSP should be used setUseSPNEGO(params.getChild("useSPNEGO") != null); } // Check if NTLMv1 logons are accepted setDisallowNTLMv1(params.getChild("disallowNTLMv1") != null); // Trigger super class initialisation super.initialize(config, params); } /** * Initialize the authenticator (after properties have been set) * * @exception InvalidConfigurationException */ @Override public void initialize() throws InvalidConfigurationException { super.initialize(); // Check if Java API Kerberos debug output should be enabled if (this.kerberosDebug) { // Enable Kerberos API debug output System.setProperty("sun.security.jgss.debug", "true"); System.setProperty("sun.security.krb5.debug", "true"); } // Check if Kerberos is enabled if (m_krbRealm != null && m_krbRealm.length() > 0) { // Get the CIFS service account password if (m_password == null || m_password.length() == 0) { throw new InvalidConfigurationException("CIFS service account password not specified"); } // Get the login configuration entry name if (m_loginEntryName == null || m_loginEntryName.length() == 0) { throw new InvalidConfigurationException("Invalid login entry specified"); } // Create a login context for the CIFS server service try { // Login the CIFS server service m_loginContext = new LoginContext(m_loginEntryName, this); m_loginContext.login(); } catch (LoginException ex) { // Debug if (logger.isErrorEnabled()) { logger.error("CIFS Kerberos authenticator error", ex); } throw new InvalidConfigurationException("Failed to login CIFS server service"); } // Get the CIFS service account name from the subject Subject subj = m_loginContext.getSubject(); Principal princ = subj.getPrincipals().iterator().next(); m_accountName = princ.getName(); if (logger.isDebugEnabled()) { logger.debug("Logged on using principal " + m_accountName); } // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback m_mechTypes = new Vector<Oid>(); // DEBUG if (logger.isDebugEnabled()) { logger.debug("Enabling mechTypes :-Kerberos5 MS-Kerberos5"); } // Always enable Kerberos m_mechTypes.add(OID.KERBEROS5); m_mechTypes.add(OID.MSKERBEROS5); if (!disableNTLM) { m_mechTypes.add(OID.NTLMSSP); // DEBUG if (logger.isDebugEnabled()) { logger.debug(" Enabling NTLMSSP"); } } // Indicate that SPNEGO security blobs are being used m_useRawNTLMSSP = false; } // Check if raw NTLMSSP or SPNEGO/NTLMSSP should be used else if (!m_useRawNTLMSSP) { // SPNEGO security blobs are being used // Create the Oid list for the SPNEGO NegTokenInit m_mechTypes = new Vector<Oid>(); m_mechTypes.add(OID.NTLMSSP); } else { // Use raw NTLMSSP security blobs } // Make sure that either Kerberos support is enabled and/or the authentication component // supports MD4 hashed passwords if (!isKerberosEnabled() && (!(getAuthenticationComponent() instanceof NLTMAuthenticator) || getNTLMAuthenticator().getNTLMMode() != NTLMMode.MD4_PROVIDER)) { if (logger.isDebugEnabled()) { logger.debug( "No valid CIFS authentication combination available, Either enable Kerberos support or use an SSO-enabled authentication component that supports MD4 hashed passwords"); } // Throw an exception to stop the CIFS server startup throw new AlfrescoRuntimeException( "No valid CIFS authentication combination available, Either enable Kerberos support or use an SSO-enabled authentication component that supports MD4 hashed passwords"); } } /** * As the mechListMIC principle may vary according to the CIFS server configuration, initialisation and retrieval of * the cached SPNEGO NegTokenInit has been moved to this method. * * @return encoded SPNEGO NegTokenInit * @throws AuthenticatorException */ private synchronized byte[] getNegTokenInit() throws AuthenticatorException { String mecListMIC = null; // Check if Kerberos is enabled byte[] encoded = null; if (m_krbRealm != null && m_krbRealm.length() > 0) { // Build the mechListMIC principle // // Note: This field is not as specified StringBuilder mic = new StringBuilder(); mic.append("cifs/"); mic.append(this.m_config.getServerName().toLowerCase()); mic.append("@"); mic.append(m_krbRealm); mecListMIC = mic.toString(); // If the principal is the same, use the cached pre-encoded version if (mecListMIC.equals(m_mecListMIC)) { encoded = m_negTokenInit; } } // Check if raw NTLMSSP or SPNEGO/NTLMSSP should be used else if (!m_useRawNTLMSSP) { encoded = m_negTokenInit; } else { return null; } if (encoded != null) { return encoded; } // Build the SPNEGO NegTokenInit blob try { // Build the SPNEGO NegTokenInit that contains the authentication types that the CIFS server accepts NegTokenInit negTokenInit = new NegTokenInit(m_mechTypes, mecListMIC); // Encode the NegTokenInit blob encoded = negTokenInit.encode(); m_negTokenInit = encoded; m_mecListMIC = mecListMIC; } catch (IOException ex) { // Debug if (logger.isErrorEnabled()) { logger.error("Unable to create SPNEGO NegTokenInit blob", ex); } throw new AuthenticatorException("Failed to create SPNEGO NegTokenInit blob"); } return encoded; } /** * Determine if Kerberos support is enabled * * @return boolean */ private final boolean isKerberosEnabled() { return m_krbRealm != null && m_loginContext != null; } /** * Determine if raw NTLMSSP or SPNEGO security blobs are being used * * @return boolean */ private final boolean useRawNTLMSSP() { return m_useRawNTLMSSP; } /** * Determine if NTLMv1 logons are accepted * * @return boolean */ private final boolean acceptNTLMv1Logon() { return m_acceptNTLMv1; } /** * JAAS callback handler * * @param callbacks Callback[] * @exception IOException * @exception UnsupportedCallbackException */ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { // Process the callback list for (int i = 0; i < callbacks.length; i++) { // Request for user name if (callbacks[i] instanceof NameCallback) { NameCallback cb = (NameCallback) callbacks[i]; // cb.setName(m_accountName); cb.setName(""); } // Request for password else if (callbacks[i] instanceof PasswordCallback) { PasswordCallback cb = (PasswordCallback) callbacks[i]; cb.setPassword(m_password.toCharArray()); } // Request for realm else if (callbacks[i] instanceof RealmCallback) { RealmCallback cb = (RealmCallback) callbacks[i]; cb.setText(m_krbRealm); } else { throw new UnsupportedCallbackException(callbacks[i]); } } } /** * Return the encryption key/challenge length * * @return int */ public int getEncryptionKeyLength() { return 8; } /** * 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 + Capability.InfoPassthru + Capability.Level2Oplocks; } /** * 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(); System.arraycopy(serverGUID.getBytes(), 0, buf, pos, 16); pos += 16; // If SPNEGO is enabled then pack the NegTokenInit blob if (useRawNTLMSSP() == false) { byte[] negTokenInit = getNegTokenInit(); System.arraycopy(negTokenInit, 0, buf, pos, m_negTokenInit.length); pos += negTokenInit.length; } // Set the negotiate response length respPkt.setByteCount(pos - respPkt.getByteOffset()); } /** * Process the CIFS session setup request packet and build the session setup response. * <p> * This is the boundary between alfresco and JLAN. So is responsible for logging and * ensuring that the exceptions are correct for JLAN. * <p> * @param sess SMBSrvSession * @param reqPkt SMBSrvPacket * @exception SMBSrvException */ public void processSessionSetup(final SMBSrvSession sess, final SMBSrvPacket reqPkt) throws SMBSrvException { try { processAlfrescoSessionSetup(sess, reqPkt); } catch (SMBSrvException e) { /* * A JLAN SMBSrvException is not human readable so we need to log the * error before throwing, rather than logging the error here. */ if (logger.isDebugEnabled()) { logger.debug("Returning SMBSrvException to JLAN", e); } throw e; } catch (AlfrescoRuntimeException a) { Throwable c = a.getCause(); if (c != null) { if (a.getCause() instanceof SMBSrvException) { logger.error(c.getMessage(), c); throw (SMBSrvException) c; } } logger.error(a.getMessage(), a); throw new SMBSrvException(SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } catch (Throwable t) { logger.error(t.getMessage(), t); throw new SMBSrvException(SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /** * Internal setup method for alfresco. There's lots of CIFS specific stuff here that should * be in JLAN. * * @param sess the JLAN session. * @param reqPkt the CIFS request packet * @throws SMBSrvException */ private void processAlfrescoSessionSetup(final SMBSrvSession sess, final SMBSrvPacket reqPkt) throws SMBSrvException { logger.debug("Start process Alfresco Session Setup"); // Check that the received packet looks like a valid NT session setup andX request if (reqPkt.checkPacketIsValid(12, 0) == false) { if (logger.isErrorEnabled()) { logger.error("Invalid packet received, return SMBStatus.NTInvalidParameter"); } throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } // Check if the request is using security blobs or the older hashed password format if (reqPkt.getParameterCount() == 13) { logger.debug("parameter count == 13, do Hashed Password Logon"); doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<Object>() { public Object execute() throws SMBSrvException { // Process the hashed password session setup logger.debug("about to call doHashedPasswordLogon"); doHashedPasswordLogon(sess, reqPkt); return null; } }); // Hashed password processing complete return; } // Extract the session details int maxBufSize = reqPkt.getParameter(2); int maxMpx = reqPkt.getParameter(3); int vcNum = reqPkt.getParameter(4); final int secBlobLen = reqPkt.getParameter(7); int capabs = reqPkt.getParameterLong(10); // Extract the client details from the session setup request int dataPos = reqPkt.getByteOffset(); final byte[] buf = reqPkt.getBuffer(); // Determine if ASCII or unicode strings are being used final boolean isUni = reqPkt.isUnicode(); // Make a note of the security blob position final 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) { logger.error("domain is null, return SMBStatus.NTInvalidParameter"); throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } // Extract the clients native operating system String clientOS = ""; if (reqPkt.hasMoreData()) { // Extract the callers operating system name clientOS = reqPkt.unpackString(isUni); if (clientOS == null) { logger.error("clientOS is null, return SMBStatus.NTInvalidParameter"); throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } if (logger.isDebugEnabled()) { logger.debug("NT Session setup " + (useRawNTLMSSP() ? "NTLMSSP" : "SPNEGO") + ", MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); } // 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 final ClientInfo client = new AlfrescoClientInfo(); 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; final boolean isNTLMSSP; 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++; } isNTLMSSP = (idx == NTLM.Signature.length); } else { isNTLMSSP = false; } /** * Extracted parameters from CIFS, Now try and do the logon */ respBlob = doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback<byte[]>() { public byte[] execute() throws Throwable { // Process the security blob if (isNTLMSSP) { // Process an NTLMSSP security blob return doNtlmsspSessionSetup(sess, client, buf, secBlobPos, secBlobLen, isUni); } else { // Process an SPNEGO security blob return doSpnegoSessionSetup(sess, client, buf, secBlobPos, secBlobLen, isUni); } } }); } catch (Exception ex) { sess.removeSetupObject(client.getProcessId()); if (ex instanceof SMBSrvException) { throw (SMBSrvException) ex; } // Convert to an access denied exception if necessary if (ex instanceof AlfrescoRuntimeException && ex.getCause() instanceof SMBSrvException) { throw (SMBSrvException) ex.getCause(); } else { logger.error("Access denied", ex); throw new SMBSrvException(SMBStatus.NTAccessDenied, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /* * We have logged on - so set up the response */ if (logger.isDebugEnabled()) { 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; // Use the original packet for the response SMBSrvPacket respPkt = reqPkt; // 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 (respBlob != null || 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; } // Set the parameter count then check if the security blob will fit into the current // packet buffer respPkt.setParameterCount(4); int reqLen = respLen + 100; // allow for strings if (reqLen > respPkt.getAvailableLength()) { try { // Allocate a new buffer for the response respPkt = sess.getPacketPool().allocatePacket(respPkt.getByteOffset() + reqLen, reqPkt); } catch (NoPooledMemoryException ex) { if (logger.isErrorEnabled()) { logger.error("Authenticator failed to allocate packet from pool, reqSiz=" + (respPkt.getByteOffset() + respLen)); } // Return a server error to the client throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVNoBuffers); } } // Fill in the rest of the packet header 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) { // Check for virtual circuit zero, disconnect any other sessions from this client if (vcNum == 0 && hasSessionCleanup()) { // Disconnect other sessions from this client, cleanup any open files/locks/oplocks int discCnt = sess.disconnectClientSessions(); // DEBUG if (discCnt > 0 && logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) logger.debug("[SMB] Disconnected " + discCnt + " existing sessions from client, sess=" + sess); } // Clear any stored session setup object for the logon sess.removeSetupObject(client.getProcessId()); // Check if the user is an administrator checkForAdminUserName(client); // Get the users home folder node, if available getHomeFolderForUser(client); // Create a virtual circuit for the new logon VirtualCircuit vc = new VirtualCircuit(vcNum, client); uid = sess.addVirtualCircuit(vc); if (uid == VirtualCircuit.InvalidUID) { logger.error("Failed to allocate UID for virtual circuit, " + vc); // Failed to allocate a UID throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVTooManyUIDs); } else if (logger.isDebugEnabled()) { 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(); byte[] buf1 = respPkt.getBuffer(); if (respBlob != null) { System.arraycopy(respBlob, 0, buf1, pos, respBlob.length); pos += respBlob.length; } // Pack the OS, dialect and domain name strings if (isUni) pos = DataPacker.wordAlign(pos); pos = DataPacker.putString("Java", buf1, pos, true, isUni); pos = DataPacker.putString("Alfresco CIFS Server " + sess.getServer().isVersion(), buf1, pos, true, isUni); pos = DataPacker.putString(getCIFSConfig().getDomainName(), buf1, 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 logger.debug("Start doNTLmsspSessionSetup"); int msgType = NTLMMessage.isNTLMType(secbuf, secpos); byte[] respBlob = null; if (msgType == -1) { // DEBUG if (logger.isErrorEnabled()) { logger.error( "Invalid NTLMSSP token received, Token= " + HexDump.hexString(secbuf, secpos, seclen, " ")); } // Return a logon failure status throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Check for a type 1 NTLMSSP message else if (msgType == NTLM.Type1) { if (logger.isDebugEnabled()) { logger.debug("NTLMsspSessionSetup 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 = new NTLanManAuthContext(); // Build a type2 message to send back to the client, containing the challenge String domain = sess.getSMBServer().getServerName(); List<TargetInfo> tList = new ArrayList<TargetInfo>(); 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.Flag128Bit + NTLM.FlagNegotiateNTLM + NTLM.FlagNegotiateUnicode + NTLM.FlagNTLM2Key + NTLM.FlagKeyExchange + NTLM.FlagTargetInfo; if (acceptNTLMv1Logon()) ntlmFlags += 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) { if (logger.isDebugEnabled()) { logger.debug("NTLmsspSessionSetup 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 logger.error("NTLMSSP Logon failure - type 2 message not found"); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Determine if the client sent us NTLMv1 or NTLMv2 if (type3Msg.hasFlag(NTLM.Flag128Bit) && type3Msg.hasFlag(NTLM.FlagNTLM2Key)) { // Determine if the client sent us an NTLMv2 blob or an NTLMv2 session key if (type3Msg.getNTLMHashLength() > 24) { // Looks like an NTLMv2 blob doNTLMv2Logon(sess, client, type3Msg); // Debug if (logger.isDebugEnabled()) { logger.debug("Logged on using NTLMSSP/NTLMv2"); } } else { // Looks like an NTLMv2 session key doNTLMv2SessionKeyLogon(sess, client, type3Msg); // Debug if (logger.isDebugEnabled()) { logger.debug("Logged on using NTLMSSP/NTLMv2SessKey"); } } } 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; } /** * Process an SPNEGO 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[] doSpnegoSessionSetup(SMBSrvSession sess, ClientInfo client, byte[] secbuf, int secpos, int seclen, boolean unicode) throws SMBSrvException { // Check the received token type, if it is a target token and there is a stored session setup object, this is the second // stage of an NTLMSSP session setup that is wrapped with SPNEGO int tokType = -1; try { tokType = SPNEGO.checkTokenType(secbuf, secpos, seclen); } catch (IOException ex) { } // Check for the second stage of an NTLMSSP logon NegTokenTarg negTarg = null; if (tokType == SPNEGO.NegTokenTarg && sess.hasSetupObject(client.getProcessId()) && sess.getSetupObject(client.getProcessId()) instanceof Type2NTLMMessage) { // Get the NTLMSSP blob from the NegTokenTarg blob NegTokenTarg negToken = new NegTokenTarg(); try { // Decode the security blob negToken.decode(secbuf, secpos, seclen); } catch (IOException ex) { // Log the error logger.error("I/O Error with SPNEGO authentication", ex); // Return a logon failure status throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Get the second stage NTLMSSP blob byte[] ntlmsspBlob = negToken.getResponseToken(); // Perform an NTLMSSP session setup byte[] ntlmsspRespBlob = doNtlmsspSessionSetup(sess, client, ntlmsspBlob, 0, ntlmsspBlob.length, unicode); // NTLMSSP is a two stage process, set the SPNEGO status int spnegoSts = SPNEGO.AcceptCompleted; if (sess.hasSetupObject(client.getProcessId())) spnegoSts = SPNEGO.AcceptIncomplete; // Package the NTLMSSP response in an SPNEGO response negTarg = new NegTokenTarg(spnegoSts, null, ntlmsspRespBlob); } else if (tokType == SPNEGO.NegTokenInit) { // Parse the SPNEGO security blob to get the Kerberos ticket NegTokenInit negToken = new NegTokenInit(); try { // Decode the security blob negToken.decode(secbuf, secpos, seclen); } catch (IOException ex) { // Log the error logger.error("Unable to decode the SPNEGO token", ex); // Return a logon failure status throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Determine the authentication mechanism the client is using and logon String oidStr = null; if (negToken.numberOfOids() > 0) oidStr = negToken.getOidAt(0).toString(); if (oidStr != null && oidStr.equals(OID.ID_NTLMSSP)) { // NTLMSSP logon, get the NTLMSSP security blob that is inside the SPNEGO blob byte[] ntlmsspBlob = negToken.getMechtoken(); // Perform an NTLMSSP session setup byte[] ntlmsspRespBlob = doNtlmsspSessionSetup(sess, client, ntlmsspBlob, 0, ntlmsspBlob.length, unicode); // NTLMSSP is a two stage process, set the SPNEGO status int spnegoSts = SPNEGO.AcceptCompleted; if (sess.hasSetupObject(client.getProcessId())) spnegoSts = SPNEGO.AcceptIncomplete; // Package the NTLMSSP response in an SPNEGO response negTarg = new NegTokenTarg(spnegoSts, OID.NTLMSSP, ntlmsspRespBlob); } else if (oidStr != null && (oidStr.equals(OID.ID_MSKERBEROS5) || oidStr.equals(OID.ID_KERBEROS5))) { // Kerberos logon negTarg = doKerberosLogon(sess, negToken, client); } else { // Debug if (logger.isDebugEnabled()) { logger.debug("No matching authentication OID found"); logger.debug(" " + negToken.toString()); } // No valid authentication mechanism logger.error("No authentication mechanism for SPNEGO found"); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Unknown SPNEGO token type logger.error("Unknown SPNEGO token type"); // Return a logon failure status throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Generate the NegTokenTarg blob byte[] respBlob = null; try { // Generate the response blob respBlob = negTarg.encode(); } catch (IOException ex) { logger.error("SPNEGO unable to encode NegTokenTarg", ex); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Return the SPNEGO response blob return respBlob; } /** * Perform a Kerberos login and return an SPNEGO response * * @param sess SMBSrvSession * @param negToken NegTokenInit * @param client ClientInfo * @return NegTokenTarg * @exception SMBSrvException */ private final NegTokenTarg doKerberosLogon(SMBSrvSession sess, NegTokenInit negToken, ClientInfo client) throws SMBSrvException { // Authenticate the user KerberosDetails krbDetails = null; NegTokenTarg negTokenTarg = null; try { // Parse the mechToken to get the AP-REQ details KerberosApReq krbApReq = new KerberosApReq(); krbApReq.parseMechToken(negToken.getMechtoken()); if (logger.isDebugEnabled()) logger.debug("Kerberos AP-REQ - " + krbApReq); // Check if mutual authentication is required KrbAuthContext krbAuthCtx = null; if (krbApReq.hasMutualAuthentication() && m_enableTicketCracking == true) { // Allocate the Kerberos authentication and parse the AP-REQ krbAuthCtx = new KrbAuthContext(); krbAuthCtx.setDebug(hasDebug()); // DEBUG if (logger.isDebugEnabled()) logger.debug("Kerberos mutual auth required, parsing AP-REQ"); try { // Parse the AP-REQ krbAuthCtx.parseKerberosApReq(m_loginContext.getSubject(), krbApReq); } catch (IOException ex) { // Failed to parse AP-REQ logger.error("Kerberos Failed to parse AP-REQ ", ex); // Return a logon failure status throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } // Run the session setup as a privileged action SessionSetupPrivilegedAction sessSetupAction = new SessionSetupPrivilegedAction(m_accountName, negToken.getMechtoken()); Object result = Subject.doAs(m_loginContext.getSubject(), sessSetupAction); if (result != null) { // Access the Kerberos response krbDetails = (KerberosDetails) result; // Determine the response OID Oid respOid = null; if (negToken.hasOid(OID.MSKERBEROS5)) { respOid = OID.MSKERBEROS5; // DEBUG if (logger.isDebugEnabled()) logger.debug("Using OID MS Kerberos5 for NegTokenTarg"); } else { respOid = OID.KERBEROS5; // DEBUG if (logger.isDebugEnabled()) logger.debug("Using OID Kerberos5 for NegTokenTarg"); } // If mutual authentication is required then we unpack the AP-REP and add in the missing // subkey that the AD client requires if (krbAuthCtx != null) { try { // Parse the AP-REP and add the missing subkey, return the updated response blob byte[] respToken = krbAuthCtx.parseKerberosApRep(krbDetails.getResponseToken()); krbDetails.setResponseToken(respToken); // Create the NegtokenTarg negTokenTarg = new NegTokenTarg(SPNEGO.AcceptCompleted, respOid, krbDetails.getResponseToken()); // DEBUG if (logger.isDebugEnabled()) logger.debug("Created NegTokenTarg using updated AP-REP, added subkey"); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("AP-REP Error:"); logger.debug(ex); } } } else { // Create the NegTokenTarg response blob negTokenTarg = new NegTokenTarg(SPNEGO.AcceptCompleted, respOid, krbDetails.getResponseToken()); // DEBUG if (logger.isDebugEnabled()) logger.debug("Created NegTokenTarg using standard Krb5 API response"); } // Check if this is a null logon String userName = krbDetails.getUserName(); String userId = m_stripKerberosUsernameSuffix ? krbDetails.getUserName() : krbDetails.getSourceName(); if (userName != null) { // Check for the machine account name // ALF-4395: Sometimes machine account name comes lowercase // and new Alfresco user is being created with machine name // if ( userName.endsWith( "$") && userName.equals( userName.toUpperCase())) if (userName.endsWith("$")) { // Null logon client.setLogonType(ClientInfo.LogonNull); // Debug if (logger.isDebugEnabled()) logger.debug("Machine account logon, " + userId + ", as null logon"); } else { // Set the current user to be authenticated, save the authentication token try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; getAuthenticationComponent().setCurrentUser(mapUserNameToPerson(userId, true)); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status logger.error("invalid user or tickets exceeded"); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Store the user name in the client information, indicate that this is not a guest logon client.setUserName(userId); client.setGuest(false); // Indicate that the session is logged on sess.setLoggedOn(true); } } else { // Null logon client.setLogonType(ClientInfo.LogonNull); } // Indicate that the session is logged on sess.setLoggedOn(true); // Debug if (logger.isDebugEnabled()) logger.debug("Logged on using Kerberos, user " + userName); } else { logger.error("No SPNEGO response, Kerberos logon failed"); // Return a logon failure status throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } catch (Exception ex) { if (ex instanceof SMBSrvException) { throw (SMBSrvException) ex; } // Return a logon failure status logger.error("Error during kerberos authentication", ex); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Return the response SPNEGO blob return negTokenTarg; } private String normalizeUserId(String externalUserId) throws SMBSrvException { try { return mapUserNameToPerson(externalUserId, true); } catch (AuthenticationException e) { // Invalid user. Return a logon failure status logger.debug("Authentication Exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /** * 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 { // Check if NTLMv1 logons are allowed if (acceptNTLMv1Logon() == false) { // NTLMv1 password hashes not accepted logger.error("NTLMv1 not accepted, client " + sess.getRemoteName()); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Get the type 2 message that contains the challenge sent to the client Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject(client.getProcessId()); sess.removeSetupObject(client.getProcessId()); // Check if we are using local MD4 password hashes or passthru authentication if (getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // 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; } // Get the stored MD4 hashed password for the user, or null if the user does not exist String normalized = normalizeUserId(userName); String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if (md4hash != null) { // 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); System.arraycopy(md4byts, 0, p21, 0, 16); // Generate the local hash of the password using the same challenge byte[] localHash = null; try { localHash = getEncryptor().doNTLM1Encryption(p21, type2Msg.getChallenge()); } catch (NoSuchAlgorithmException ex) { } // Validate the password byte[] clientHash = type3Msg.getNTLMHash(); if (clientHash != null && localHash != null && clientHash.length == localHash.length) { int i = 0; while (i < clientHash.length && clientHash[i] == localHash[i]) i++; if (i != clientHash.length) { // Return a logon failure if (logger.isDebugEnabled()) { logger.debug("NTLMV1 - hash was not equal"); } throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /* * Setup the Alfresco Security (Acegi) context */ try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; getAuthenticationComponent().setCurrentUser(normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status logger.debug("Authentication Exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Store the full user name in the client information, indicate that this is not a guest logon client.setUserName(userName.toLowerCase()); client.setGuest(false); // Indicate that the session is logged on sess.setLoggedOn(true); } else { if (logger.isDebugEnabled()) { logger.debug("User does not exist, " + userName); } // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, authentication component does not support MD4 hashed passwords logger.warn("Authentication component does not support MD4 password hashes"); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /** * Perform an NTLMv1 logon using the NTLMSSP type3 message * * @param sess SMBSrvSession * @param client ClientInfo * @exception SMBSrvException */ private final void doNTLMv1Logon(SMBSrvSession sess, ClientInfo client) throws SMBSrvException { // Check if NTLMv1 logons are allowed if (acceptNTLMv1Logon() == false) { // NTLMv1 password hashes not accepted logger.warn("NTLMv1 not accepted, client " + sess.getRemoteName()); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } if (logger.isDebugEnabled()) { logger.debug("START doNTLMv1Logon:" + client); } // Check if we are using local MD4 password hashes or passthru authentication if (getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // Check for a null logon if (client.getUserName().length() == 0) { // DEBUG if (logger.isDebugEnabled()) { logger.debug("Null logon"); } // Indicate a null logon in the client information client.setLogonType(ClientInfo.LogonNull); return; } // Get the stored MD4 hashed password for the user, or null if the user does not exist String normalized = normalizeUserId(client.getUserName()); String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if (md4hash != null) { // 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); System.arraycopy(md4byts, 0, p21, 0, 16); // Get the challenge that was sent to the client during negotiation byte[] challenge = null; if (sess.hasAuthenticationContext()) { // Get the challenge from the authentication context NTLanManAuthContext ntlmCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); challenge = ntlmCtx.getChallenge(); } // Generate the local hash of the password using the same challenge byte[] localHash = null; try { localHash = getEncryptor().doNTLM1Encryption(p21, challenge); } catch (NoSuchAlgorithmException ex) { logger.error("Unable to encrypt challenge", ex); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVInternalServerError); } // Validate the password byte[] clientHash = client.getPassword(); if (clientHash != null && localHash != null && clientHash.length == localHash.length) { int i = 0; while (i < clientHash.length && clientHash[i] == localHash[i]) i++; if (i != clientHash.length) { // Return a logon failure if (logger.isDebugEnabled()) { logger.debug("NTLMV1 access denied - wrong password"); } throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } // Setup the Acegi authenticated user try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; getAuthenticationComponent().setCurrentUser(normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status logger.debug("Authentication exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Store the full user name in the client information, indicate that this is not a guest logon client.setGuest(false); // Indicate that the session is logged on sess.setLoggedOn(true); } else { // Log a warning, user does not exist logger.warn("User does not exist, " + client.getUserName()); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, authentication component does not support MD4 hashed passwords logger.error("Authentication component does not support MD4 password hashes"); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /** * Perform an NTLMv2 logon using the NTLMSSP type3 message * * @param sess SMBSrvSession * @param client ClientInfo * @param type3Msg Type3NTLMMessage * @exception SMBSrvException */ private final void doNTLMv2Logon(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()); // Check if we are using local MD4 password hashes or passthru authentication if (logger.isDebugEnabled()) { logger.debug("START doNTLMv2Logon:" + client); } if (getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // 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; } // Get the stored MD4 hashed password for the user, or null if the user does not exist String normalized = normalizeUserId(userName); String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if (md4hash != null) { try { // Generate the v2 hash using the challenge that was sent to the client byte[] v2hash = getEncryptor().doNTLM2Encryption(m_md4Encoder.decodeHash(md4hash), type3Msg.getUserName(), type3Msg.getDomain()); // Get the NTLMv2 blob sent by the client and the challenge that was sent by the server NTLMv2Blob v2blob = new NTLMv2Blob(type3Msg.getNTLMHash()); byte[] srvChallenge = type2Msg.getChallenge(); // Calculate the HMAC of the received blob and compare byte[] srvHmac = v2blob.calculateHMAC(srvChallenge, v2hash); byte[] clientHmac = v2blob.getHMAC(); if (clientHmac != null && srvHmac != null && clientHmac.length == srvHmac.length) { int i = 0; while (i < clientHmac.length && clientHmac[i] == srvHmac[i]) i++; if (i != clientHmac.length) { // Return a logon failure if (logger.isDebugEnabled()) { logger.debug("wrong password"); } throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } // Setup the Acegi authenticated user AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; getAuthenticationComponent().setCurrentUser(normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); // Store the full user name in the client information, indicate that this is not a guest logon client.setUserName(userName.toLowerCase()); client.setGuest(false); // Indicate that the session is logged on sess.setLoggedOn(true); } catch (Exception ex) { if (ex instanceof SMBSrvException) { throw (SMBSrvException) ex; } // Log the error if (ex instanceof AuthenticationException) { logger.debug(ex.getMessage(), ex); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else { logger.error(ex.getMessage(), ex); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } } else { // Log a warning, user does not exist logger.warn("MD4Hash for User does not exist, " + userName); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, authentication component does not support MD4 hashed passwords logger.warn("Authentication component does not support MD4 password hashes"); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /** * Perform an NTLMv2 logon using the NTLMSSP type3 message * * @param sess SMBSrvSession * @param client ClientInfo * @exception SMBSrvException */ private final void doNTLMv2Logon(SMBSrvSession sess, ClientInfo client) throws SMBSrvException { // Check if we are using local MD4 password hashes or passthru authentication if (logger.isDebugEnabled()) { logger.debug("START doNTLMv2Logon:" + client); } if (getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // Check for a null logon if (client.getUserName().length() == 0) { // DEBUG if (logger.isDebugEnabled()) { logger.debug("Null logon"); } // Indicate a null logon in the client information client.setLogonType(ClientInfo.LogonNull); return; } // Get the stored MD4 hashed password for the user, or null if the user does not exist String normalized = normalizeUserId(client.getUserName()); String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if (md4hash != null) { try { // Create the NTLMv2 blob from the received hashed password bytes NTLMv2Blob v2blob = new NTLMv2Blob(client.getPassword()); // Generate the v2 hash using the challenge that was sent to the client byte[] v2hash = getEncryptor().doNTLM2Encryption(m_md4Encoder.decodeHash(md4hash), client.getUserName(), client.getDomain()); // Get the challenge that was sent to the client during negotiation byte[] srvChallenge = null; if (sess.hasAuthenticationContext()) { // Get the challenge from the authentication context NTLanManAuthContext ntlmCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); srvChallenge = ntlmCtx.getChallenge(); } // Calculate the HMAC of the received blob and compare byte[] srvHmac = v2blob.calculateHMAC(srvChallenge, v2hash); byte[] clientHmac = v2blob.getHMAC(); if (clientHmac != null && srvHmac != null && clientHmac.length == srvHmac.length) { int i = 0; while (i < clientHmac.length && clientHmac[i] == srvHmac[i]) i++; if (i != clientHmac.length) { logger.debug("bad client hmac"); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } // Setup the Acegi authenticated user AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; getAuthenticationComponent().setCurrentUser(normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); // Store the full user name in the client information, indicate that this is not a guest logon client.setGuest(false); // Indicate that the session is logged on sess.setLoggedOn(true); } catch (Exception ex) { // Log the error if (ex instanceof AuthenticationException) { // Return a logon failure logger.debug(ex.getMessage(), ex); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else { logger.error(ex.getMessage(), ex); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } } else { // Log a warning, user does not exist logger.warn("User does not exist, " + client.getUserName()); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, authentication component does not support MD4 hashed passwords logger.warn("Authentication component does not support MD4 password hashes"); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /** * Perform an NTLMv2 session key logon * * @param sess SMBSrvSession * @param client ClientInfo * @param type3Msg Type3NTLMMessage * @exception SMBSrvException */ private final void doNTLMv2SessionKeyLogon(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()); // Check if we are using local MD4 password hashes or passthru authentication if (getNTLMAuthenticator().getNTLMMode() == NTLMMode.MD4_PROVIDER) { // 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; } // Get the stored MD4 hashed password for the user, or null if the user does not exist String normalized = normalizeUserId(userName); String md4hash = getNTLMAuthenticator().getMD4HashedPassword(normalized); if (md4hash != null) { // Create the value to be encrypted by appending the server challenge and client challenge // and applying an MD5 digest byte[] nonce = new byte[16]; System.arraycopy(type2Msg.getChallenge(), 0, nonce, 0, 8); System.arraycopy(type3Msg.getLMHash(), 0, nonce, 8, 8); MessageDigest md5 = null; byte[] v2challenge = new byte[8]; try { // Create the MD5 digest md5 = MessageDigest.getInstance("MD5"); // Apply the MD5 digest to the nonce md5.update(nonce); byte[] md5nonce = md5.digest(); // We only want the first 8 bytes System.arraycopy(md5nonce, 0, v2challenge, 0, 8); } catch (NoSuchAlgorithmException ex) { logger.error(ex); } // Generate the local encrypted password using the MD5 generated challenge byte[] p21 = new byte[21]; byte[] md4byts = m_md4Encoder.decodeHash(md4hash); System.arraycopy(md4byts, 0, p21, 0, 16); // Generate the local hash of the password byte[] localHash = null; try { localHash = getEncryptor().doNTLM1Encryption(p21, v2challenge); } catch (NoSuchAlgorithmException ex) { // Log the error logger.error(ex); } // Validate the password byte[] clientHash = type3Msg.getNTLMHash(); if (clientHash != null && localHash != null && clientHash.length == localHash.length) { int i = 0; while (i < clientHash.length && clientHash[i] == localHash[i]) i++; if (i != clientHash.length) { logger.debug("bad client hash"); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrSrv, SMBStatus.SRVBadPassword); } } // Setup the Acegi authenticated user try { AlfrescoClientInfo alfClient = (AlfrescoClientInfo) client; getAuthenticationComponent().setCurrentUser(normalized); alfClient.setAuthenticationTicket(getAuthenticationService().getCurrentTicket()); } catch (AuthenticationException e) { // Invalid user or max tickets exceeded. Return a logon failure status logger.debug("Authentication exception", e); throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Store the full user name in the client information, indicate that this is not a guest logon client.setUserName(userName.toLowerCase()); client.setGuest(false); // Indicate that the session is logged on sess.setLoggedOn(true); } else { // Log a warning, user does not exist logger.warn("User does not exist, " + userName); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, authentication component does not support MD4 hashed passwords logger.warn("Authentication component does not support MD4 password hashes"); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /** * Perform a hashed password logon using either NTLMv1 or NTLMv2 * * @param sess SMBSrvSession * @param reqPkt SMBSrvPacket * @exception SMBSrvException */ private final void doHashedPasswordLogon(SMBSrvSession sess, SMBSrvPacket reqPkt) throws SMBSrvException { // Check that the received packet looks like a valid NT session setup andX request if (reqPkt.checkPacketIsValid(13, 0) == false) { throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } // Extract the session details int maxBufSize = reqPkt.getParameter(2); int maxMpx = reqPkt.getParameter(3); int vcNum = reqPkt.getParameter(4); int ascPwdLen = reqPkt.getParameter(7); int uniPwdLen = reqPkt.getParameter(8); int capabs = reqPkt.getParameterLong(11); // Extract the client details from the session setup request byte[] buf = reqPkt.getBuffer(); // Determine if ASCII or unicode strings are being used boolean isUni = reqPkt.isUnicode(); // Extract the password strings byte[] ascPwd = reqPkt.unpackBytes(ascPwdLen); byte[] uniPwd = reqPkt.unpackBytes(uniPwdLen); // Extract the user name string String user = reqPkt.unpackString(isUni); if (user == null) { logger.error("User not specified"); throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } // Extract the clients primary domain name string String domain = ""; if (reqPkt.hasMoreData()) { // Extract the callers domain name domain = reqPkt.unpackString(isUni); if (domain == null) { logger.error("Domain not specified"); throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } // Extract the clients native operating system String clientOS = ""; if (reqPkt.hasMoreData()) { // Extract the callers operating system name clientOS = reqPkt.unpackString(isUni); if (clientOS == null) { logger.error("client OS not specified"); throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } } // DEBUG if (logger.isDebugEnabled()) { logger.debug("NT Session setup from user=" + user + ", password=" + (uniPwd != null ? HexDump.hexString(uniPwd) : "none") + ", ANSIpwd=" + (ascPwd != null ? HexDump.hexString(ascPwd) : "none") + ", domain=" + domain + ", os=" + clientOS + ", VC=" + vcNum + ", maxBuf=" + maxBufSize + ", maxMpx=" + maxMpx + ", authCtx=" + sess.getAuthenticationContext()); logger.debug(" MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId() + ", PID=" + reqPkt.getProcessId()); } // Store the client maximum buffer size, maximum multiplexed requests count and client // capability flags sess.setClientMaximumBufferSize(maxBufSize); sess.setClientMaximumMultiplex(maxMpx); sess.setClientCapabilities(capabs); // Create the client information and store in the session ClientInfo client = new AlfrescoClientInfo(user, uniPwd); client.setANSIPassword(ascPwd); client.setDomain(domain); client.setOperatingSystem(clientOS); if (sess.hasRemoteAddress()) client.setClientAddress(sess.getRemoteAddress().getHostAddress()); // Check if this is a null session logon if (user.length() == 0 && domain.length() == 0 && uniPwdLen == 0) client.setLogonType(ClientInfo.LogonNull); // Authenticate the user using the Unicode password hash, this is either NTLMv1 or NTLMv2 encoded boolean isGuest = false; if (uniPwd != null) { if (uniPwd.length == 24) { // NTLMv1 hashed password doNTLMv1Logon(sess, client); // Debug if (logger.isDebugEnabled()) { logger.debug("Logged on using Hashed/NTLMv1"); } } else if (uniPwd.length > 0) { // NTLMv2 blob doNTLMv2Logon(sess, client); // Debug if (logger.isDebugEnabled()) { logger.debug("Logged on using Hashed/NTLMv2"); } } } // Check if the user was logged on as guest if (client.isGuest()) { // Guest logon isGuest = true; // DEBUG if (logger.isDebugEnabled()) logger.debug("User " + user + ", logged on as guest"); } // Create a virtual circuit and allocate a UID to the new circuit VirtualCircuit vc = new VirtualCircuit(vcNum, client); int uid = sess.addVirtualCircuit(vc); if (uid == VirtualCircuit.InvalidUID) { logger.error("Failed to allocate UID for virtual circuit, " + vc); // Failed to allocate a UID throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else if (logger.isDebugEnabled()) { 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); sess.setLoggedOn(true); // Build the session setup response SMB reqPkt.setParameterCount(3); reqPkt.setParameter(0, 0); // No chained response reqPkt.setParameter(1, 0); // Offset to chained response reqPkt.setParameter(2, isGuest ? 1 : 0); reqPkt.setByteCount(0); reqPkt.setTreeId(0); reqPkt.setUserId(uid); // Set the various flags int flags = reqPkt.getFlags(); flags &= ~SMBSrvPacket.FLG_CASELESS; reqPkt.setFlags(flags); int flags2 = SMBSrvPacket.FLG2_LONGFILENAMES; if (isUni) flags2 += SMBSrvPacket.FLG2_UNICODE; reqPkt.setFlags2(flags2); // Pack the OS, dialect and domain name strings. int pos = reqPkt.getByteOffset(); buf = reqPkt.getBuffer(); 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(getCIFSConfig().getDomainName(), buf, pos, true, isUni); reqPkt.setByteCount(pos - reqPkt.getByteOffset()); } }