/* * Copyright (C) 2006-2008 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.jlan.server.auth; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; 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.jlan.debug.Debug; import org.alfresco.jlan.netbios.RFCNetBIOSProtocol; 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.CIFSConfigSection; 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.springframework.extensions.config.ConfigElement; import org.ietf.jgss.Oid; /** * Enterprise CIFS Authenticator Class * * <p> * CIFS authenticator that supports NTLMSSP and Kerberos logins. * * @author gkspencer */ public class EnterpriseCifsAuthenticator extends CifsAuthenticator implements CallbackHandler { // Constants // // Default login configuration entry name private static final String LoginConfigEntry = "JLANServerCIFS"; // 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; // Flag to control whether NTLMv1 is accepted private boolean m_acceptNTLMv1; // 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 and KDC address private String m_krbRealm; private String m_krbKDC; // 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 byte[] m_negTokenInit; /** * Class constructor */ public EnterpriseCifsAuthenticator() { setExtendedSecurity(true); } /** * Initialize the authenticator * * @param config ServerConfiguration * @param params ConfigElement * @exception InvalidConfigurationException */ public void initialize(ServerConfiguration config, ConfigElement params) throws InvalidConfigurationException { // Call the base initialization super.initialize(config, params); // Check if Java API Kerberos debug output should be enabled if ( params.getChild("kerberosDebug") != null) { // Enable Kerberos API debug output System.setProperty( "sun.security.jgss.debug", "true"); System.setProperty( "sun.security.krb5.debug", "true"); } // Access the CIFS server configuration CIFSConfigSection cifsConfig = (CIFSConfigSection) config.getConfigSection(CIFSConfigSection.SectionName); // Check if Kerberos is enabled, get the Kerberos KDC address ConfigElement kdcAddress = params.getChild("KDC"); if ( kdcAddress != null && kdcAddress.getValue() != null && kdcAddress.getValue().length() > 0) { // Set the Kerberos KDC address m_krbKDC = kdcAddress.getValue(); // Get the Kerberos realm ConfigElement krbRealm = params.getChild("Realm"); if ( krbRealm != null && krbRealm.getValue() != null && krbRealm.getValue().length() > 0) { // Set the Kerberos realm m_krbRealm = krbRealm.getValue(); } else throw new InvalidConfigurationException("Kerberos realm not specified"); // Get the CIFS service account password ConfigElement srvPassword = params.getChild("Password"); if ( srvPassword != null && srvPassword.getValue() != null && srvPassword.getValue().length() > 0) { // Set the CIFS service account password m_password = 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 m_loginEntryName = loginEntry.getValue(); } else throw new InvalidConfigurationException("Invalid login entry specified"); } // Get the server principal name ConfigElement principal = params.getChild("Principal"); if ( principal != null) { // Use the supplied principal name to build the account name StringBuffer cifsAccount = new StringBuffer(); cifsAccount.append(principal.getValue()); cifsAccount.append("@"); cifsAccount.append(m_krbRealm); m_accountName = cifsAccount.toString(); } else { // Build the CIFS service account name StringBuffer cifsAccount = new StringBuffer(); cifsAccount.append("cifs/"); cifsAccount.append(cifsConfig.getServerName().toLowerCase()); cifsAccount.append("@"); cifsAccount.append(m_krbRealm); m_accountName = cifsAccount.toString(); } // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Using principal - " + m_accountName); // 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 ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] CIFS Kerberos authenticator error - " + ex.getMessage()); throw new InvalidConfigurationException("Failed to login CIFS server service"); } // DEBUG if ( Debug.EnableDbg && hasDebug()) { Debug.println("[SMB] Enabling mechTypes :-"); Debug.println(" Kerberos5"); Debug.println(" MS-Kerberos5"); } // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback Vector<Oid> mechTypes = new Vector<Oid>(); mechTypes.add(OID.KERBEROS5); mechTypes.add(OID.MSKERBEROS5); if ( params.getChild("disableNTLM") == null) { mechTypes.add(OID.NTLMSSP); // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println(" NTLMSSP"); } // Build the SPNEGO NegTokenInit blob try { // Build the mechListMIC principle // // Note: This field is not as specified, only seem sto be used by Samba clients (Linux/Mac/Unix) String mecListMIC = null; StringBuffer mic = new StringBuffer(); mic.append("cifs/"); mic.append(cifsConfig.getServerName().toLowerCase()); mic.append("@"); mic.append(m_krbRealm); mecListMIC = mic.toString(); // Build the SPNEGO NegTokenInit that contains the authentication types that the // CIFS server accepts NegTokenInit negTokenInit = new NegTokenInit(mechTypes, mecListMIC); // Encode the NegTokenInit blob m_negTokenInit = negTokenInit.encode(); } catch (IOException ex) { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Error creating SPNEGO NegTokenInit blob - " + ex.getMessage()); throw new InvalidConfigurationException("Failed to create SPNEGO NegTokenInit blob"); } // Indicate that SPNEGO security blobs are being used m_useRawNTLMSSP = false; } else { // Check if raw NTLMSSP or SPNEGO/NTLMSSP should be used ConfigElement useSpnego = params.getChild("useSPNEGO"); if ( useSpnego != null) { // Create the Oid list for the SPNEGO NegTokenInit Vector mechTypes = new Vector(); mechTypes.add(OID.NTLMSSP); // Build the SPNEGO NegTokenInit blob try { // Build the SPNEGO NegTokenInit that contains the authentication types that the // CIFS server accepts NegTokenInit negTokenInit = new NegTokenInit(mechTypes, null); // Encode the NegTokenInit blob m_negTokenInit = negTokenInit.encode(); } catch (IOException ex) { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Error creating SPNEGO NegTokenInit blob - " + ex.getMessage()); throw new InvalidConfigurationException("Failed to create SPNEGO NegTokenInit blob"); } // Indicate that SPNEGO security blobs are being used m_useRawNTLMSSP = false; } else { // Use raw NTLMSSP security blobs m_useRawNTLMSSP = true; } } // Check if NTLMv1 logons are accepted ConfigElement disallowNTLMv1 = params.getChild("disallowNTLMv1"); m_acceptNTLMv1 = disallowNTLMv1 != null ? false : true; } /** * 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); } // 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; } /** * 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 int flags2 = respPkt.getFlags2(); flags2 |= SMBSrvPacket.FLG2_EXTENDEDSECURITY + SMBSrvPacket.FLG2_LONGERRORCODE; respPkt.setFlags2(flags2); // 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) { System.arraycopy(m_negTokenInit, 0, buf, pos, m_negTokenInit.length); pos += m_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 * * @param sess SMBSrvSession * @param reqPkt SMBSrvPacket * @exception SMBSrvException */ public void processSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt) 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) { try { // Process the hashed password session setup doHashedPasswordLogon(sess, reqPkt); return; } catch (SMBSrvException ex) { // Rethrow the exception throw ex; } } // 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); } // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] 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 ClientInfo client = ClientInfo.createInfo(null, null); 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) { // Process an NTLMSSP security blob respBlob = doNtlmsspSessionSetup(sess, client, buf, secBlobPos, secBlobLen, isUni); } else { // Process an SPNEGO security blob respBlob = doSpnegoSessionSetup(sess, client, buf, secBlobPos, secBlobLen, isUni); } } catch (SMBSrvException ex) { // Remove the session setup object for this logon attempt sess.removeSetupObject(client.getProcessId()); // Rethrow the exception throw ex; } // Debug if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) { if ( respBlob == null) Debug.println("[SMB] User " + client.getUserName() + " logged on " + (client != null ? " (type " + client.getLogonTypeString() + ")" : "")); else Debug.println("[SMB] Two stage logon (" + (isNTLMSSP ? "NTLMSSP" : "SPNEGO") + ")"); } // 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) { // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("Authenticator failed to allocate packet from pool, reqSiz=" + (respPkt.getByteOffset() + respLen)); // Return a server error to the client throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNoBuffers, SMBStatus.ErrSrv); } } // 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, vcNum); // 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 ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) Debug.println("[SMB] Failed to allocate UID for virtual circuit, " + vc); // Failed to allocate a UID throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); } else if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) { // DEBUG Debug.println("[SMB] 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); if ( respBlob == null) pos = DataPacker.putString(sess.getSMBServer().getCIFSConfiguration().getDomainName(), buf, pos, true, isUni); respPkt.setByteCount(pos - respPkt.getByteOffset()); respPkt.setParameter(1, pos - RFCNetBIOSProtocol.HEADER_LEN); } /** * 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 ( Debug.EnableInfo && hasDebug()) { Debug.println("[SMB] Invalid NTLMSSP token received"); Debug.println("[SMB] 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) { // 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 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.toLowerCase())); tList.add(new TargetInfo(NTLM.TargetFullDNS, domain.toLowerCase())); ntlmFlags = NTLM.FlagChallengeAccept + NTLM.FlagRequestTarget + NTLM.Flag128Bit + NTLM.FlagNegotiateNTLM + NTLM.FlagNegotiateUnicode + NTLM.FlagNTLM2Key + NTLM.FlagKeyExchange + NTLM.FlagTargetInfo; if ( acceptNTLMv1Logon()) ntlmFlags += NTLM.Flag56Bit; 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.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 ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Logged on using NTLMSSP/NTLMv2"); } else { // Looks like an NTLMv2 session key doNTLMv2SessionKeyLogon(sess, client, type3Msg); // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Logged on using NTLMSSP/NTLMv2SessKey"); } } else { // Looks like an NTLMv1 blob doNTLMv1Logon(sess, client, type3Msg); // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] 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 if ( Debug.EnableError && hasDebug()) Debug.println(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 if ( Debug.EnableError && hasDebug()) Debug.println(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 ( Debug.EnableInfo && hasDebug()) { Debug.println("[SMB] No matching authentication OID found"); Debug.println("[SMB] " + negToken.toString()); } // No valid authentication mechanism throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Unknown SPNEGO token type if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] 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) { // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Failed to encode NegTokenTarg - " + ex.getMessage()); // Failed to build response blob 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 ( Debug.EnableDbg && hasDebug()) Debug.println( "[SMB] Kerberos AP-REQ - " + krbApReq); // Check if mutual authentication is required KrbAuthContext krbAuthCtx = null; if ( krbApReq.hasMutualAuthentication()) { // Allocate the Kerberos authentication and parse the AP-REQ krbAuthCtx = new KrbAuthContext(); krbAuthCtx.setDebug(hasDebug()); // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] 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 if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Failed to parse AP-REQ, " + ex.toString()); // 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 ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Using OID MS Kerberos5 for NegTokenTarg"); } else { respOid = OID.KERBEROS5; // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] 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 ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Created NegTokenTarg using updated AP-REP, added subkey"); } catch (Exception ex) { if ( Debug.EnableDbg && hasDebug()) { Debug.println("[SMB] AP-REP Error:"); Debug.println( ex); } } } else { // Create the NegTokenTarg response blob negTokenTarg = new NegTokenTarg( SPNEGO.AcceptCompleted, respOid, krbDetails.getResponseToken()); // DEBUG if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Created NegTokenTarg using standard Krb5 API response"); } // Check if this is a null logon String userName = krbDetails.getUserName(); if ( userName != null) { // Check for the machine account name if ( userName.endsWith( "$") && userName.equals( userName.toUpperCase())) { // Null logon client.setLogonType( ClientInfo.LogonNull); // Debug if ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Machine account logon, " + userName + ", as null logon"); } else { // Store the full user name in the client information, indicate that this is not a guest logon client.setUserName( krbDetails.getSourceName()); 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 ( Debug.EnableDbg && hasDebug()) Debug.println("[SMB] Logged on using Kerberos, user " + userName); } else { // Debug if ( Debug.EnableDbg && hasDebug()) Debug.println( "[SMB] No SPNEGO response, Kerberos logon failed"); // Return a logon failure status throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } catch (Exception ex) { // Log the error if ( Debug.EnableError && hasDebug()) { Debug.println("[SMB] Kerberos logon error"); Debug.println(ex); } // Return a logon failure status throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Return the response SPNEGO blob return negTokenTarg; } /** * 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 if ( Debug.EnableWarn && hasDebug()) Debug.println("[SMB] 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()); // Get the NTLM logon details String userName = type3Msg.getUserName(); // Check for a null logon if ( userName.length() == 0) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Null logon"); // Indicate a null logon in the client information client.setLogonType(ClientInfo.LogonNull); return; } // Get the user details UserAccount user = getUserDetails(userName); if ( user != null) { // Authenticate the user int sts = authenticateUser(client, sess, NTLM1); if ( sts < 0) { // Logon failed 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); client.setGuest(sts == AUTH_GUEST ? true : false); // Indicate that the session is logged on sess.setLoggedOn(true); } else { // Log a warning, user does not exist if ( Debug.EnableError && hasDebug()) Debug.println("[SMB] User does not exist, " + userName); // 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 if ( Debug.EnableWarn && hasDebug()) Debug.println("[SMB] NTLMv1 not accepted, client " + sess.getRemoteName()); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Get the user details String userName = client.getUserName(); UserAccount user = getUserDetails(userName); if ( user != null) { // Authenticate the user int sts = authenticateUser(client, sess, NTLM1); if ( sts < 0) { // Logon failed 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); client.setGuest(sts == AUTH_GUEST ? true : false); // Indicate that the session is logged on sess.setLoggedOn(true); } else { // Log a warning, user does not exist if ( Debug.EnableError && hasDebug()) Debug.println("[SMB] User does not exist, " + userName); // 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()); // Get the NTLM logon details String userName = type3Msg.getUserName(); // Check for a null logon if ( userName.length() == 0) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Null logon"); // Indicate a null logon in the client information client.setLogonType(ClientInfo.LogonNull); return; } // Get the user details UserAccount user = getUserDetails(userName); if ( user != null) { try { // Calculate the MD4 of the user password byte[] md4Pwd = null; if ( user.hasMD4Password()) md4Pwd = user.getMD4Password(); else { md4Pwd = getEncryptor().generateEncryptedPassword(user.getPassword(), type2Msg.getChallenge(), PasswordEncryptor.MD4, null, null); user.setMD4Password(md4Pwd); } // Generate the v2 hash using the challenge that was sent to the client byte[] v2hash = getEncryptor().doNTLM2Encryption(md4Pwd, 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 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); client.setGuest(false); // Indicate that the session is logged on sess.setLoggedOn(true); } catch (Exception ex) { // Log the error if ( Debug.EnableError && hasDebug()) Debug.println(ex); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, user does not exist if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] User does not exist, " + userName); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } /** * Perform an NTLMv2 logon * * @param sess SMBSrvSession * @param client ClientInfo * @exception SMBSrvException */ private final void doNTLMv2Logon(SMBSrvSession sess, ClientInfo client) throws SMBSrvException { // Check for a null logon if ( client.getUserName().length() == 0) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Null logon"); // Indicate a null logon in the client information client.setLogonType(ClientInfo.LogonNull); return; } // Get the user details UserAccount user = getUserDetails(client.getUserName()); if ( user != null) { try { // 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 MD4 of the user password byte[] md4Pwd = null; if ( user.hasMD4Password()) md4Pwd = user.getMD4Password(); else { md4Pwd = getEncryptor().generateEncryptedPassword(user.getPassword(), srvChallenge, PasswordEncryptor.MD4, null, null); user.setMD4Password(md4Pwd); } // 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(md4Pwd, client.getUserName(), client.getDomain()); // 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 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); } catch (Exception ex) { // Log the error if ( Debug.EnableError && hasDebug()) Debug.println(ex); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } else { // Log a warning, user does not exist if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] User does not exist, " + client.getUserName()); // 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()); // Get the NTLM logon details String userName = type3Msg.getUserName(); // Check for a null logon if ( userName.length() == 0) { // DEBUG if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Null logon"); // Indicate a null logon in the client information client.setLogonType(ClientInfo.LogonNull); return; } // Get the user details UserAccount user = getUserDetails(userName); if ( user != 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) { // Log the error if ( Debug.EnableError && hasDebug()) Debug.println(ex); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } // Generate the local encrypted password using the MD5 generated challenge byte[] p21 = new byte[21]; byte[] md4byts = null; if ( user.hasMD4Password()) md4byts = user.getMD4Password(); else { try { md4byts = getEncryptor().generateEncryptedPassword(user.getPassword(), null, PasswordEncryptor.MD4, null, null); } catch (Exception ex) { // DEBUG if ( Debug.EnableError && hasDebug()) Debug.println(ex); // Return a logon failure throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } } 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 if ( Debug.EnableError && hasDebug()) Debug.println(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 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); client.setGuest(false); // Indicate that the session is logged on sess.setLoggedOn(true); } else { // Log a warning, user does not exist if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] User does not exist, " + userName); // 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) 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) 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) throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.ErrSrv, SMBStatus.SRVNonSpecificError); } // DEBUG if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) { Debug.println("[SMB] 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()); Debug.println("[SMB] 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 ClientInfo client = ClientInfo.getFactory().createInfo(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 ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Logged on using Hashed/NTLMv1"); } else if ( uniPwd.length > 0) { // NTLMv2 blob doNTLMv2Logon(sess, client); // Debug if ( Debug.EnableInfo && hasDebug()) Debug.println("[SMB] Logged on using Hashed/NTLMv2"); } } // Check if the user was logged on as guest if ( client.isGuest()) { // Guest logon isGuest = true; // DEBUG if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) Debug.println("[SMB] 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) { // DEBUG if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) Debug.println("[SMB] Failed to allocate UID for virtual circuit, " + vc); // Failed to allocate a UID throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied); } else if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) { // DEBUG Debug.println("[SMB] 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(sess.getSMBServer().getCIFSConfiguration().getDomainName(), buf, pos, true, isUni); reqPkt.setByteCount(pos - reqPkt.getByteOffset()); } }