From d021b46d07d5bf5f210f008d70014eadeca49352 Mon Sep 17 00:00:00 2001 From: Gary Spencer Date: Thu, 4 May 2006 15:29:26 +0000 Subject: [PATCH] Redesign of the CIFS authentication code to support NTLMv1/NTLMv2, SPNEGO and NTLMSSP authentication methods via the session setup. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2760 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../filesys/server/NetworkServer.java | 8 +- .../alfresco/filesys/server/SrvSession.java | 71 +- .../server/auth/AlfrescoAuthenticator.java | 74 +- .../filesys/server/auth/AuthContext.java | 29 + .../server/auth/AuthenticatorException.java | 45 + .../server/auth/CifsAuthenticator.java | 893 ++++++++ .../filesys/server/auth/ClientInfo.java | 10 +- .../server/auth/DefaultAuthenticator.java | 120 - .../auth/EnterpriseCifsAuthenticator.java | 2017 +++++++++++++++++ .../server/auth/LocalAuthenticator.java | 216 -- .../server/auth/NTLanManAuthContext.java | 88 + .../server/auth/PasswordEncryptor.java | 96 +- .../filesys/server/auth/SrvAuthenticator.java | 596 ----- .../filesys/server/auth/UserAccount.java | 331 --- .../filesys/server/auth/UserAccountList.java | 195 -- .../server/auth/kerberos/KerberosDetails.java | 134 ++ .../SessionSetupPrivilegedAction.java | 113 + .../auth/ntlm/AlfrescoAuthenticator.java | 51 +- .../filesys/server/auth/ntlm/NTLM.java | 7 +- .../filesys/server/auth/ntlm/NTLMMessage.java | 6 +- .../filesys/server/auth/ntlm/NTLMv2Blob.java | 187 ++ .../server/auth/ntlm/Type3NTLMMessage.java | 20 + .../passthru/AcegiPassthruAuthenticator.java | 289 --- .../auth/passthru/PassthruAuthenticator.java | 88 +- .../server/auth/spnego/NegTokenInit.java | 407 ++++ .../server/auth/spnego/NegTokenTarg.java | 262 +++ .../filesys/server/auth/spnego/OID.java | 74 + .../filesys/server/auth/spnego/SPNEGO.java | 132 ++ .../server/config/ServerConfiguration.java | 284 +-- 29 files changed, 4719 insertions(+), 2124 deletions(-) create mode 100644 source/java/org/alfresco/filesys/server/auth/AuthContext.java create mode 100644 source/java/org/alfresco/filesys/server/auth/AuthenticatorException.java create mode 100644 source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java delete mode 100644 source/java/org/alfresco/filesys/server/auth/DefaultAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java delete mode 100644 source/java/org/alfresco/filesys/server/auth/LocalAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/auth/NTLanManAuthContext.java delete mode 100644 source/java/org/alfresco/filesys/server/auth/SrvAuthenticator.java delete mode 100644 source/java/org/alfresco/filesys/server/auth/UserAccount.java delete mode 100644 source/java/org/alfresco/filesys/server/auth/UserAccountList.java create mode 100644 source/java/org/alfresco/filesys/server/auth/kerberos/KerberosDetails.java create mode 100644 source/java/org/alfresco/filesys/server/auth/kerberos/SessionSetupPrivilegedAction.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/NTLMv2Blob.java delete mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/AcegiPassthruAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/auth/spnego/NegTokenInit.java create mode 100644 source/java/org/alfresco/filesys/server/auth/spnego/NegTokenTarg.java create mode 100644 source/java/org/alfresco/filesys/server/auth/spnego/OID.java create mode 100644 source/java/org/alfresco/filesys/server/auth/spnego/SPNEGO.java diff --git a/source/java/org/alfresco/filesys/server/NetworkServer.java b/source/java/org/alfresco/filesys/server/NetworkServer.java index e07482f6f7..86007136ac 100644 --- a/source/java/org/alfresco/filesys/server/NetworkServer.java +++ b/source/java/org/alfresco/filesys/server/NetworkServer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Alfresco, Inc. + * Copyright (C) 2005-2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -19,7 +19,7 @@ package org.alfresco.filesys.server; import java.net.InetAddress; import java.util.Vector; -import org.alfresco.filesys.server.auth.SrvAuthenticator; +import org.alfresco.filesys.server.auth.CifsAuthenticator; import org.alfresco.filesys.server.auth.acl.AccessControlManager; import org.alfresco.filesys.server.config.ServerConfiguration; import org.alfresco.filesys.server.core.ShareMapper; @@ -99,9 +99,9 @@ public abstract class NetworkServer /** * Return the authenticator for this server * - * @return SrvAuthenticator + * @return CifsAuthenticator */ - public final SrvAuthenticator getAuthenticator() + public final CifsAuthenticator getAuthenticator() { return getConfiguration().getAuthenticator(); } diff --git a/source/java/org/alfresco/filesys/server/SrvSession.java b/source/java/org/alfresco/filesys/server/SrvSession.java index bd659ff8d4..097a9db9a3 100644 --- a/source/java/org/alfresco/filesys/server/SrvSession.java +++ b/source/java/org/alfresco/filesys/server/SrvSession.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Alfresco, Inc. + * Copyright (C) 2005-2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -23,6 +23,7 @@ import javax.transaction.SystemException; import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.server.auth.AuthContext; import org.alfresco.filesys.server.auth.ClientInfo; import org.alfresco.filesys.server.core.SharedDevice; import org.alfresco.filesys.server.core.SharedDeviceList; @@ -60,10 +61,6 @@ public abstract class SrvSession private ClientInfo m_clientInfo; - // Challenge key used for this session - - private byte[] m_challenge; - // Debug flags for this session private int m_debug; @@ -85,6 +82,10 @@ public abstract class SrvSession private Object m_authToken; + // Authentication context, used during the initial session setup phase + + private AuthContext m_authContext; + // List of dynamic/temporary shares created for this session private SharedDeviceList m_dynamicShares; @@ -154,26 +155,6 @@ public abstract class SrvSession return m_authToken != null ? true : false; } - /** - * Return the session challenge key - * - * @return byte[] - */ - public final byte[] getChallengeKey() - { - return m_challenge; - } - - /** - * Determine if the challenge key has been set for this session - * - * @return boolean - */ - public final boolean hasChallengeKey() - { - return m_challenge != null ? true : false; - } - /** * Return the process id * @@ -231,6 +212,26 @@ public abstract class SrvSession return m_clientInfo; } + /** + * Check if the session has an authentication context + * + * @return boolean + */ + public final boolean hasAuthenticationContext() + { + return m_authContext != null ? true : false; + } + + /** + * Return the authentication context for this sesion + * + * @return AuthContext + */ + public final AuthContext getAuthenticationContext() + { + return m_authContext; + } + /** * Determine if the session has any dynamic shares * @@ -342,6 +343,16 @@ public abstract class SrvSession m_authToken = authToken; } + /** + * Set the authentication context, used during the initial session setup phase + * + * @param ctx AuthContext + */ + public final void setAuthenticationContext( AuthContext ctx) + { + m_authContext = ctx; + } + /** * Set the client information * @@ -352,16 +363,6 @@ public abstract class SrvSession m_clientInfo = client; } - /** - * Set the session challenge key - * - * @param key byte[] - */ - public final void setChallengeKey(byte[] key) - { - m_challenge = key; - } - /** * Set the debug output interface. * diff --git a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java index c3afddc45f..ec20bd579e 100644 --- a/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/AlfrescoAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Alfresco, Inc. + * Copyright (C) 2005-2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -20,7 +20,6 @@ import java.security.NoSuchAlgorithmException; import org.alfresco.filesys.server.SrvSession; import org.alfresco.filesys.smb.server.SMBSrvSession; -import org.alfresco.filesys.util.DataPacker; import org.alfresco.repo.security.authentication.NTLMMode; /** @@ -34,7 +33,7 @@ import org.alfresco.repo.security.authentication.NTLMMode; * * @author GKSpencer */ -public class AlfrescoAuthenticator extends SrvAuthenticator +public class AlfrescoAuthenticator extends CifsAuthenticator { /** * Default Constructor @@ -43,8 +42,6 @@ public class AlfrescoAuthenticator extends SrvAuthenticator */ public AlfrescoAuthenticator() { - setAccessMode(SrvAuthenticator.USER_MODE); - setEncryptedPasswords(true); } /** @@ -82,7 +79,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator if ( logger.isDebugEnabled()) logger.debug("Null CIFS logon allowed"); - return SrvAuthenticator.AUTH_ALLOW; + return CifsAuthenticator.AUTH_ALLOW; } // Check if the client is already authenticated, and it is not a null logon @@ -175,46 +172,6 @@ public class AlfrescoAuthenticator extends SrvAuthenticator return authSts; } - /** - * Generate a challenge key - * - * @param sess SrvSession - * @return byte[] - */ - public byte[] getChallengeKey(SrvSession sess) - { - // In MD4 mode we generate the challenge locally - - byte[] key = null; - - // Check if the client is already authenticated, and it is not a null logon - - if ( sess.hasClientInformation() && sess.getClientInformation().getAuthenticationToken() != null && - sess.getClientInformation().getLogonType() != ClientInfo.LogonNull) - { - // Return the previous challenge, user is already authenticated - - key = sess.getChallengeKey(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Re-using existing challenge, already authenticated"); - } - else if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) - { - // Generate a new challenge key, pack the key and return - - key = new byte[8]; - - DataPacker.putIntelLong(m_random.nextLong(), key, 0); - } - - // Return the challenge - - return key; - } - /** * Perform MD4 user authentication * @@ -234,7 +191,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Check if the client has supplied an NTLM hashed password, if not then do not allow access if ( client.getPassword() == null) - return SrvAuthenticator.AUTH_BADPASSWORD; + return CifsAuthenticator.AUTH_BADPASSWORD; try { @@ -244,21 +201,30 @@ public class AlfrescoAuthenticator extends SrvAuthenticator byte[] md4byts = m_md4Encoder.decodeHash(md4hash); System.arraycopy(md4byts, 0, p21, 0, 16); + // Get the challenge that was sent to the client + + NTLanManAuthContext authCtx = null; + + if ( sess.hasAuthenticationContext() && sess.getAuthenticationContext() instanceof NTLanManAuthContext) + authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); + else + return CifsAuthenticator.AUTH_DISALLOW; + // Generate the local hash of the password using the same challenge - byte[] localHash = getEncryptor().doNTLM1Encryption(p21, sess.getChallengeKey()); + byte[] localHash = getEncryptor().doNTLM1Encryption(p21, authCtx.getChallenge()); // Validate the password byte[] clientHash = client.getPassword(); if ( clientHash == null || clientHash.length != localHash.length) - return SrvAuthenticator.AUTH_BADPASSWORD; + return CifsAuthenticator.AUTH_BADPASSWORD; for ( int i = 0; i < clientHash.length; i++) { if ( clientHash[i] != localHash[i]) - return SrvAuthenticator.AUTH_BADPASSWORD; + return CifsAuthenticator.AUTH_BADPASSWORD; } // Set the current user to be authenticated, save the authentication token @@ -271,7 +237,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Passwords match, grant access - return SrvAuthenticator.AUTH_ALLOW; + return CifsAuthenticator.AUTH_ALLOW; } catch (NoSuchAlgorithmException ex) { @@ -279,7 +245,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Error during password check, do not allow access - return SrvAuthenticator.AUTH_DISALLOW; + return CifsAuthenticator.AUTH_DISALLOW; } // Check if this is an SMB/CIFS null session logon. @@ -287,10 +253,10 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // The null session will only be allowed to connect to the IPC$ named pipe share. if (client.isNullSession() && sess instanceof SMBSrvSession) - return SrvAuthenticator.AUTH_ALLOW; + return CifsAuthenticator.AUTH_ALLOW; // User does not exist, check if guest access is allowed - return allowGuest() ? SrvAuthenticator.AUTH_GUEST : SrvAuthenticator.AUTH_DISALLOW; + return allowGuest() ? CifsAuthenticator.AUTH_GUEST : CifsAuthenticator.AUTH_DISALLOW; } } \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/AuthContext.java b/source/java/org/alfresco/filesys/server/auth/AuthContext.java new file mode 100644 index 0000000000..d291e1c530 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/AuthContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth; + +/** + * CIFS Authentication Context Class + * + *

Holds authentication specific information for the negotiate/session setup phase of a new CIFS session. + * + * @author gkspencer + */ +public class AuthContext +{ + +} diff --git a/source/java/org/alfresco/filesys/server/auth/AuthenticatorException.java b/source/java/org/alfresco/filesys/server/auth/AuthenticatorException.java new file mode 100644 index 0000000000..6fa2bc4a48 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/AuthenticatorException.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth; + +/** + * Authenticator Exception Class + * + * @author gkspencer + */ +public class AuthenticatorException extends Exception +{ + private static final long serialVersionUID = 7816213724352083486L; + + /** + * Default constructor. + */ + public AuthenticatorException() + { + super(); + } + + /** + * Class constructor. + * + * @param s String + */ + public AuthenticatorException(String s) + { + super(s); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java new file mode 100644 index 0000000000..d21b62e627 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/CifsAuthenticator.java @@ -0,0 +1,893 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth; + +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Random; + +import javax.transaction.UserTransaction; + +import net.sf.acegisecurity.Authentication; + +import org.alfresco.config.ConfigElement; +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.config.InvalidConfigurationException; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.filesys.server.core.SharedDevice; +import org.alfresco.filesys.server.filesys.DiskDeviceContext; +import org.alfresco.filesys.server.filesys.DiskInterface; +import org.alfresco.filesys.server.filesys.DiskSharedDevice; +import org.alfresco.filesys.server.filesys.SrvDiskInfo; +import org.alfresco.filesys.smb.Capability; +import org.alfresco.filesys.smb.Dialect; +import org.alfresco.filesys.smb.DialectSelector; +import org.alfresco.filesys.smb.SMBStatus; +import org.alfresco.filesys.smb.server.SMBSrvException; +import org.alfresco.filesys.smb.server.SMBSrvPacket; +import org.alfresco.filesys.smb.server.SMBSrvSession; +import org.alfresco.filesys.smb.server.SecurityMode; +import org.alfresco.filesys.smb.server.repo.ContentContext; +import org.alfresco.filesys.util.DataPacker; +import org.alfresco.filesys.util.HexDump; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.MD4PasswordEncoder; +import org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * CIFS Authenticator Class + * + *

+ * An authenticator is used by the CIFS server to authenticate users when in user level access mode + * and authenticate requests to connect to a share when in share level access. + */ +public abstract class CifsAuthenticator +{ + // Logging + + protected static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); + + // Encryption algorithm types + + public static final int LANMAN = PasswordEncryptor.LANMAN; + public static final int NTLM1 = PasswordEncryptor.NTLM1; + public static final int NTLM2 = PasswordEncryptor.NTLM2; + + // Authentication status values + + public static final int AUTH_ALLOW = 0; + public static final int AUTH_GUEST = 0x10000000; + public static final int AUTH_DISALLOW = -1; + public static final int AUTH_BADPASSWORD = -2; + public static final int AUTH_BADUSER = -3; + + // Share access permissions, returned by authenticateShareConnect() + + public static final int NoAccess = 0; + public static final int ReadOnly = 1; + public static final int Writeable = 2; + + // Standard encrypted password and challenge length + + public static final int STANDARD_PASSWORD_LEN = 24; + public static final int STANDARD_CHALLENGE_LEN = 8; + + // Default guest user name + + protected static final String GUEST_USERNAME = "guest"; + + // Default SMB dialects to enable + + private DialectSelector m_dialects; + + // Security mode flags + + private int m_securityMode = SecurityMode.UserMode + SecurityMode.EncryptedPasswords; + + // Password encryption algorithms + + private PasswordEncryptor m_encryptor = new PasswordEncryptor(); + + // Flag to enable/disable the guest account, and control mapping of unknown users to the guest account + + private boolean m_allowGuest; + private boolean m_mapToGuest; + + // Default guest user name + + private String m_guestUserName = GUEST_USERNAME; + + // Random number generator used to generate challenge keys + + protected Random m_random = new Random(System.currentTimeMillis()); + + // Server configuration + + protected ServerConfiguration m_config; + + // Authentication component, used to access internal authentication functions + + protected AuthenticationComponent m_authComponent; + + // MD4 hash decoder + + protected MD4PasswordEncoder m_md4Encoder = new MD4PasswordEncoderImpl(); + + // Various services required to get user information + + protected NodeService m_nodeService; + protected PersonService m_personService; + protected TransactionService m_transactionService; + protected AuthenticationService m_authenticationService; + + /** + * Authenticate a connection to a share. + * + * @param client User/client details from the tree connect request. + * @param share Shared device the client wants to connect to. + * @param pwd Share password. + * @param sess Server session. + * @return int Granted file permission level or disallow status if negative. See the + * FilePermission class. + */ + public int authenticateShareConnect(ClientInfo client, SharedDevice share, String sharePwd, SrvSession sess) + { + // Allow write access + // + // Main authentication is handled by authenticateUser() + + return CifsAuthenticator.Writeable; + } + + /** + * Authenticate a user. A user may be granted full access, guest access or no access. + * + * @param client User/client details from the session setup request. + * @param sess Server session + * @param alg Encryption algorithm + * @return int Access level or disallow status. + */ + public int authenticateUser(ClientInfo client, SrvSession sess, int alg) + { + return CifsAuthenticator.AUTH_DISALLOW; + } + + /** + * Initialize the authenticator + * + * @param config ServerConfiguration + * @param params ConfigElement + * @exception InvalidConfigurationException + */ + public void initialize(ServerConfiguration config, ConfigElement params) throws InvalidConfigurationException + { + // Save the server configuration so we can access the authentication component + + m_config = config; + + // Check that the required authentication classes are available + + m_authComponent = m_config.getAuthenticationComponent(); + + if ( m_authComponent == null) + throw new InvalidConfigurationException("Authentication component not available"); + + // Allocate the SMB dialect selector, and initialize using the default list of dialects + + m_dialects = new DialectSelector(); + + m_dialects.AddDialect(Dialect.DOSLanMan1); + m_dialects.AddDialect(Dialect.DOSLanMan2); + m_dialects.AddDialect(Dialect.LanMan1); + m_dialects.AddDialect(Dialect.LanMan2); + m_dialects.AddDialect(Dialect.LanMan2_1); + m_dialects.AddDialect(Dialect.NT); + + // Get hold of various services + + m_nodeService = config.getNodeService(); + m_personService = config.getPersonService(); + m_transactionService = config.getTransactionService(); + m_authenticationService = config.getAuthenticationService(); + + // Set the guest user name + + setGuestUserName( m_authComponent.getGuestUserName()); + + // Check that the authentication component is the required type for this authenticator + + if ( validateAuthenticationMode() == false) + throw new InvalidConfigurationException("Required authentication mode not available"); + } + + /** + * Validate that the authentication component supports the required mode + * + * @return boolean + */ + protected boolean validateAuthenticationMode() + { + return true; + } + + /** + * Encrypt the plain text password with the specified encryption key using the specified + * encryption algorithm. + * + * @param plainPwd String + * @param encryptKey byte[] + * @param alg int + * @param userName String + * @param domain String + * @return byte[] + */ + protected final byte[] generateEncryptedPassword(String plainPwd, byte[] encryptKey, int alg, String userName, String domain) + { + + // Use the password encryptor + + byte[] encPwd = null; + + try + { + // Encrypt the password + + encPwd = m_encryptor.generateEncryptedPassword(plainPwd, encryptKey, alg, userName, domain); + } + catch (NoSuchAlgorithmException ex) + { + } + catch (InvalidKeyException ex) + { + } + + // Return the encrypted password + + return encPwd; + } + + /** + * Return an authentication context for the new session + * + * @return AuthContext + */ + public AuthContext getAuthContext( SMBSrvSession sess) + { + AuthContext authCtx = null; + + if ( sess.hasAuthenticationContext() && sess.getAuthenticationContext() instanceof NTLanManAuthContext) + { + // Use the existing authentication context + + authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); + } + else + { + // Create a new authentication context for the session + + authCtx = new NTLanManAuthContext(); + sess.setAuthenticationContext( authCtx); + } + + // Return the authentication context + + return authCtx; + } + + /** + * Return the enabled SMB dialects that the server will use when negotiating sessions. + * + * @return DialectSelector + */ + public final DialectSelector getEnabledDialects() + { + return m_dialects; + } + + /** + * Return the security mode flags + * + * @return int + */ + public final int getSecurityMode() + { + return m_securityMode; + } + + /** + * 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 + { + // Pack the negotiate response for NT/LanMan challenge/response authentication + + NTLanManAuthContext authCtx = (NTLanManAuthContext) getAuthContext( sess); + + // Encryption key and primary domain string should be returned in the byte area + + int pos = respPkt.getByteOffset(); + byte[] buf = respPkt.getBuffer(); + + if ( authCtx.getChallenge() == null) + { + + // Return a dummy encryption key + + for (int i = 0; i < 8; i++) + buf[pos++] = 0; + } + else + { + + // Store the encryption key + + byte[] key = authCtx.getChallenge(); + for (int i = 0; i < key.length; i++) + buf[pos++] = key[i]; + } + + // Pack the local domain name + + String domain = sess.getServer().getConfiguration().getDomainName(); + if (domain != null) + pos = DataPacker.putString(domain, buf, pos, true, true); + + // Pack the local server name + + pos = DataPacker.putString( sess.getServer().getServerName(), buf, pos, true, true); + + // Set the packet length + + respPkt.setByteCount(pos - respPkt.getByteOffset()); + } + + /** + * Process the CIFS session setup request packet and build the session setup response + * + * @param sess SMBSrvSession + * @param reqPkt SMBSrvPacket + * @param respPkt SMBSrvPacket + * @exception SMBSrvException + */ + public void processSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt, SMBSrvPacket respPkt) + throws SMBSrvException + { + + // Check that the received packet looks like a valid NT session setup andX request + + if (reqPkt.checkPacketIsValid(13, 0) == false) + { + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + } + + // 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.SRVNonSpecificError, SMBStatus.ErrSrv); + } + + // 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.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 (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + { + 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 ClientInfo(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 && ascPwdLen == 1) + client.setLogonType(ClientInfo.LogonNull); + + // Authenticate the user + + boolean isGuest = false; + + int sts = authenticateUser(client, sess, CifsAuthenticator.NTLM1); + + if (sts > 0 && (sts & CifsAuthenticator.AUTH_GUEST) != 0) + { + + // Guest logon + + isGuest = true; + + // DEBUG + + if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("User " + user + ", logged on as guest"); + } + else if (sts != CifsAuthenticator.AUTH_ALLOW) + { + + // Check if the session already has valid client details and the new client details + // have null username/password values + + if (sess.getClientInformation() != null && client.getUserName().length() == 0) + { + + // Use the existing client information details + + client = sess.getClientInformation(); + + // DEBUG + + if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("Null client information, reusing existing client=" + client); + } + else + { + // DEBUG + + if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("User " + user + ", access denied"); + + // Invalid user, reject the session setup request + + throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + else if (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + { + + // DEBUG + + logger.debug("User " + user + " 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); + } + + // 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 + + respPkt.setParameterCount(3); + respPkt.setParameter(0, 0); // No chained response + respPkt.setParameter(1, 0); // Offset to chained response + respPkt.setParameter(2, isGuest ? 1 : 0); + respPkt.setByteCount(0); + + respPkt.setTreeId(0); + respPkt.setUserId(0); + + // Set the various flags + + int flags = respPkt.getFlags(); + flags &= ~SMBSrvPacket.FLG_CASELESS; + respPkt.setFlags(flags); + + int flags2 = SMBSrvPacket.FLG2_LONGFILENAMES; + if (isUni) + flags2 += SMBSrvPacket.FLG2_UNICODE; + respPkt.setFlags2(flags2); + + // Pack the OS, dialect and domain name strings. + + int pos = respPkt.getByteOffset(); + buf = respPkt.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.getServer().getConfiguration().getDomainName(), buf, pos, true, isUni); + + respPkt.setByteCount(pos - respPkt.getByteOffset()); + } + + /** + * Return the encryption key/challenge length + * + * @return int + */ + public int getEncryptionKeyLength() + { + return STANDARD_CHALLENGE_LEN; + } + + /** + * 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; + } + + /** + * Determine if guest access is allowed + * + * @return boolean + */ + public final boolean allowGuest() + { + return m_allowGuest; + } + + /** + * Return the guest user name + * + * @return String + */ + public final String getGuestUserName() + { + return m_guestUserName; + } + + /** + * Determine if unknown users should be mapped to the guest account + * + * @return boolean + */ + public final boolean mapUnknownUserToGuest() + { + return m_mapToGuest; + } + + /** + * Enable/disable the guest account + * + * @param ena Enable the guest account if true, only allow defined user accounts access if false + */ + public final void setAllowGuest(boolean ena) + { + m_allowGuest = ena; + } + + /** + * Set the guest user name + * + * @param guest String + */ + public final void setGuestUserName( String guest) + { + m_guestUserName = guest; + } + + /** + * Enable/disable mapping of unknown users to the guest account + * + * @param ena Enable mapping of unknown users to the guest if true + */ + public final void setMapToGuest( boolean ena) + { + m_mapToGuest = ena; + } + + /** + * Set the security mode flags + * + * @param flg int + */ + protected final void setSecurityMode(int flg) + { + m_securityMode = flg; + } + + /** + * Close the authenticator, perform any cleanup + */ + public void closeAuthenticator() + { + // Override if cleanup required + } + + /** + * Validate a password by encrypting the plain text password using the specified encryption key + * and encryption algorithm. + * + * @param plainPwd String + * @param encryptedPwd String + * @param encryptKey byte[] + * @param alg int + * @param userName String + * @param domain String + * @return boolean + */ + protected final boolean validatePassword(String plainPwd, byte[] encryptedPwd, byte[] encryptKey, int alg, String userName, String domain) + { + + // Generate an encrypted version of the plain text password + + byte[] encPwd = generateEncryptedPassword(plainPwd != null ? plainPwd : "", encryptKey, alg, userName, domain); + + // Compare the generated password with the received password + + if (encPwd != null && encryptedPwd != null && encPwd.length == STANDARD_PASSWORD_LEN + && encryptedPwd.length == STANDARD_PASSWORD_LEN) + { + + // Compare the password arrays + + for (int i = 0; i < STANDARD_PASSWORD_LEN; i++) + if (encPwd[i] != encryptedPwd[i]) + return false; + + // Password is valid + + return true; + } + + // User or password is invalid + + return false; + } + + /** + * Convert the password string to a byte array + * + * @param pwd String + * @return byte[] + */ + + protected final byte[] convertPassword(String pwd) + { + + // Create a padded/truncated 14 character string + + StringBuffer p14str = new StringBuffer(); + p14str.append(pwd); + if (p14str.length() > 14) + p14str.setLength(14); + else + { + while (p14str.length() < 14) + p14str.append((char) 0x00); + } + + // Convert the P14 string to an array of bytes. Allocate the return 16 byte array. + + return p14str.toString().getBytes(); + } + + /** + * Return the password encryptor + * + * @return PasswordEncryptor + */ + protected final PasswordEncryptor getEncryptor() + { + return m_encryptor; + } + + /** + * Return the authentication status as a string + * + * @param sts int + * @return String + */ + protected final String getStatusAsString(int sts) + { + String str = null; + + switch ( sts) + { + case AUTH_ALLOW: + str = "Allow"; + break; + case AUTH_DISALLOW: + str = "Disallow"; + break; + case AUTH_GUEST: + str = "Guest"; + break; + case AUTH_BADPASSWORD: + str = "BadPassword"; + break; + case AUTH_BADUSER: + str = "BadUser"; + break; + } + + return str; + } + + /** + * Logon using the guest user account + * + * @param client ClientInfo + * @param sess SrvSession + */ + protected final void doGuestLogon( ClientInfo client, SrvSession sess) + { + // Get a guest authentication token + + m_authenticationService.authenticateAsGuest(); + Authentication authToken = m_authComponent.getCurrentAuthentication(); + + client.setAuthenticationToken( authToken); + + // Set the home folder for the guest user + + client.setUserName( getGuestUserName()); + getHomeFolderForUser( client); + + // Mark the client as being a guest logon + + client.setGuest( true); + + // Create a dynamic share for the guest user + // Create the disk driver and context + + DiskInterface diskDrv = m_config.getDiskInterface(); + DiskDeviceContext diskCtx = new ContentContext("", "", client.getHomeFolder()); + + // Default the filesystem to look like an 80Gb sized disk with 90% free space + + diskCtx.setDiskInformation(new SrvDiskInfo(2560, 64, 512, 2304)); + + // Create a temporary shared device for the users home directory + + sess.addDynamicShare( new DiskSharedDevice( client.getUserName(), diskDrv, diskCtx, SharedDevice.Temporary)); + } + + /** + * Get the home folder for the user + * + * @param client ClientInfo + */ + protected final void getHomeFolderForUser(ClientInfo client) + { + // Get the home folder for the user + + UserTransaction tx = m_transactionService.getUserTransaction(); + NodeRef homeSpaceRef = null; + + try + { + tx.begin(); + homeSpaceRef = (NodeRef) m_nodeService.getProperty(m_personService.getPerson(client.getUserName()), + ContentModel.PROP_HOMEFOLDER); + client.setHomeFolder( homeSpaceRef); + tx.commit(); + } + catch (Throwable ex) + { + try + { + tx.rollback(); + } + catch (Throwable ex2) + { + logger.error("Failed to rollback transaction", ex2); + } + + // Re-throw the exception + if (ex instanceof RuntimeException) + { + throw (RuntimeException) ex; + } + else + { + throw new RuntimeException("Error during execution of transaction.", ex); + } + } + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/ClientInfo.java b/source/java/org/alfresco/filesys/server/auth/ClientInfo.java index d2418ae5fa..56eec14aeb 100644 --- a/source/java/org/alfresco/filesys/server/auth/ClientInfo.java +++ b/source/java/org/alfresco/filesys/server/auth/ClientInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Alfresco, Inc. + * Copyright (C) 2005-2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -74,6 +74,14 @@ public class ClientInfo // Home folder node private NodeRef m_homeNode; + + /** + * Default constructor + */ + public ClientInfo() + { + setUserName(""); + } /** * Class constructor diff --git a/source/java/org/alfresco/filesys/server/auth/DefaultAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/DefaultAuthenticator.java deleted file mode 100644 index 952f234123..0000000000 --- a/source/java/org/alfresco/filesys/server/auth/DefaultAuthenticator.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.auth; - -import org.alfresco.config.ConfigElement; -import org.alfresco.filesys.server.SrvSession; -import org.alfresco.filesys.server.config.InvalidConfigurationException; -import org.alfresco.filesys.server.config.ServerConfiguration; -import org.alfresco.filesys.server.core.SharedDevice; - -/** - *

- * Default authenticator class. - *

- * The default authenticator implementation enables user level security mode and allows any user to - * connect to the server. - */ -public class DefaultAuthenticator extends SrvAuthenticator -{ - - // Server configuration - - private ServerConfiguration m_config; - - /** - * Class constructor - */ - public DefaultAuthenticator() - { - setAccessMode(USER_MODE); - setEncryptedPasswords(true); - } - - /** - * Allow any user to access the server - * - * @param client Client details. - * @param share Shared device the user is connecting to. - * @param pwd Share level password. - * @param sess Server session - * @return int - */ - public int authenticateShareConnect(ClientInfo client, SharedDevice share, String pwd, SrvSession sess) - { - return Writeable; - } - - /** - * Allow any user to access the server. - * - * @param client Client details. - * @param sess Server session - * @param alg Encryption algorithm - * @return int - */ - public int authenticateUser(ClientInfo client, SrvSession sess, int alg) - { - return AUTH_ALLOW; - } - - /** - * The default authenticator does not use encrypted passwords. - * - * @param sess SrvSession - * @return byte[] - */ - public byte[] getChallengeKey(SrvSession sess) - { - return null; - } - - /** - * Search for the requried user account details in the defined user list - * - * @param user String - * @return UserAccount - */ - public UserAccount getUserDetails(String user) - { - - // Get the user account list from the configuration - - UserAccountList userList = m_config.getUserAccounts(); - if (userList == null || userList.numberOfUsers() == 0) - return null; - - // Search for the required user account record - - return userList.findUser(user); - } - - /** - * Initialzie the authenticator - * - * @param config ServerConfiguration - * @param params ConfigElement - * @exception InvalidConfigurationException - */ - public void initialize(ServerConfiguration config, ConfigElement params) throws InvalidConfigurationException - { - - // Save the server configuration so we can access the defined user list - - m_config = config; - } -} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java new file mode 100644 index 0000000000..3bf487c3ff --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/EnterpriseCifsAuthenticator.java @@ -0,0 +1,2017 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.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.UUID; +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.config.ConfigElement; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.server.auth.AuthenticatorException; +import org.alfresco.filesys.server.auth.CifsAuthenticator; +import org.alfresco.filesys.server.auth.ClientInfo; +import org.alfresco.filesys.server.auth.NTLanManAuthContext; +import org.alfresco.filesys.server.auth.kerberos.KerberosDetails; +import org.alfresco.filesys.server.auth.kerberos.SessionSetupPrivilegedAction; +import org.alfresco.filesys.server.auth.ntlm.NTLM; +import org.alfresco.filesys.server.auth.ntlm.NTLMMessage; +import org.alfresco.filesys.server.auth.ntlm.NTLMv2Blob; +import org.alfresco.filesys.server.auth.ntlm.TargetInfo; +import org.alfresco.filesys.server.auth.ntlm.Type1NTLMMessage; +import org.alfresco.filesys.server.auth.ntlm.Type2NTLMMessage; +import org.alfresco.filesys.server.auth.ntlm.Type3NTLMMessage; +import org.alfresco.filesys.server.auth.spnego.NegTokenInit; +import org.alfresco.filesys.server.auth.spnego.NegTokenTarg; +import org.alfresco.filesys.server.auth.spnego.OID; +import org.alfresco.filesys.server.auth.spnego.SPNEGO; +import org.alfresco.filesys.server.config.InvalidConfigurationException; +import org.alfresco.filesys.server.config.ServerConfiguration; +import org.alfresco.filesys.smb.Capability; +import org.alfresco.filesys.smb.SMBStatus; +import org.alfresco.filesys.smb.server.SMBSrvException; +import org.alfresco.filesys.smb.server.SMBSrvPacket; +import org.alfresco.filesys.smb.server.SMBSrvSession; +import org.alfresco.filesys.util.DataPacker; +import org.alfresco.filesys.util.HexDump; +import org.alfresco.repo.security.authentication.NTLMMode; +import org.ietf.jgss.Oid; + +/** + * Enterprise CIFS Authenticator Class + * + *

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 = "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; + + // 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/@ + + 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() + { + } + + /** + * Initialize the authenticator + * + * @param config ServerConfiguration + * @param params ConfigElement + * @exception InvalidConfigurationException + */ + public void initialize(ServerConfiguration config, ConfigElement params) throws InvalidConfigurationException + { + super.initialize(config, params); + + // 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"); + } + + // Build the CIFS service account name + + StringBuilder cifsAccount = new StringBuilder(); + + cifsAccount.append("cifs/"); + cifsAccount.append( config.getServerName().toLowerCase()); + cifsAccount.append("@"); + cifsAccount.append(m_krbRealm); + + m_accountName = cifsAccount.toString(); + + // 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"); + } + + // Create the Oid list for the SPNEGO NegTokenInit, include NTLMSSP for fallback + + Vector mechTypes = new Vector(); + + mechTypes.add(OID.NTLMSSP); + mechTypes.add(OID.KERBEROS5); + mechTypes.add(OID.MSKERBEROS5); + + // Build the SPNEGO NegTokenInit blob + + try + { + // Build the mechListMIC principle + // + // Note: This field is not as specified + + String mecListMIC = null; + + StringBuilder mic = new StringBuilder(); + mic.append( config.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 ( logger.isErrorEnabled()) + logger.error("Error creating SPNEGO NegTokenInit blob", ex); + + 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 ( logger.isErrorEnabled()) + logger.error("Error creating SPNEGO NegTokenInit blob", ex); + + 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; + + // Make sure that either Kerberos support is enabled and/or the authentication component + // supports MD4 hashed passwords + + if ( isKerberosEnabled() == false && m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER) + { + // Log an error + + logger.error("No valid CIFS authentication combination available"); + logger.error("Either enable Kerberos support or use an authentication component that supports MD4 hashed passwords"); + + // Throw an exception to stop the CIFS server startup + + throw new AlfrescoRuntimeException("Invalid CIFS authenticator configuration"); + } + } + + /** + * Determine if Kerberos support is enabled + * + * @return boolean + */ + private final boolean isKerberosEnabled() + { + return m_krbKDC != 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); + } + + // 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 + + if (( respPkt.getFlags2() & SMBSrvPacket.FLG2_EXTENDEDSECURITY) == 0) + respPkt.setFlags2( respPkt.getFlags2() + SMBSrvPacket.FLG2_EXTENDEDSECURITY); + + // Get the negotiate response byte area position + + int pos = respPkt.getByteOffset(); + byte[] buf = respPkt.getBuffer(); + + // Pack the CIFS server GUID into the negotiate response + + UUID serverGUID = sess.getSMBServer().getServerGUID(); + + DataPacker.putIntelLong( serverGUID.getLeastSignificantBits(), buf, pos); + DataPacker.putIntelLong( serverGUID.getMostSignificantBits(), buf, pos + 8); + + pos += 16; + + // 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 + * @param respPkt SMBSrvPacket + * @exception SMBSrvException + */ + public void processSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt, SMBSrvPacket respPkt) + throws SMBSrvException + { + // Check that the received packet looks like a valid NT session setup andX request + + if (reqPkt.checkPacketIsValid(12, 0) == false) + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + + // Check if the request is using security blobs or the older hashed password format + + if ( reqPkt.getParameterCount() == 13) + { + try + { + // Process the hashed password session setup + + doHashedPasswordLogon( sess, reqPkt, respPkt); + return; + } + catch (SMBSrvException ex) + { + // Cleanup any stored context + + sess.setSetupObject( null); + + // Rethrow the exception + + throw ex; + } + } + + // Extract the session details + + int maxBufSize = reqPkt.getParameter(2); + int maxMpx = reqPkt.getParameter(3); + 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 (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); + sess.setClientMaximumMultiplex(maxMpx); + sess.setClientCapabilities(capabs); + + // Create the client information and store in the session + + ClientInfo client = new ClientInfo(); + client.setDomain(domain); + client.setOperatingSystem(clientOS); + + client.setLogonType( ClientInfo.LogonNormal); + + // Set the remote address, if available + + if ( sess.hasRemoteAddress()) + client.setClientAddress(sess.getRemoteAddress().getHostAddress()); + + // Save the setup object, if valid + + Object setupObj = sess.getSetupObject(); + + // Process the security blob + + byte[] respBlob = null; + + try + { + if ( useRawNTLMSSP()) + { + // 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) + { + // Cleanup any stored context + + sess.setSetupObject( null); + + // Rethrow the exception + + throw ex; + } + + // Debug + + if ( logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("User " + client.getUserName() + " logged on " + (client != null ? " (type " + client.getLogonTypeString() + ")" : "")); + + // Update the client information if not already set + + if ( sess.getClientInformation() == null || + sess.getClientInformation().getUserName().length() == 0) { + + // Set the client details for the session + + sess.setClientInformation(client); + } + + // Get the response blob length, it can be null + + int respLen = respBlob != null ? respBlob.length : 0; + + // Check if there is/was a session setup object stored in the session, this indicates a multi-stage session + // setup so set the status code accordingly + + if ( useRawNTLMSSP() || sess.hasSetupObject() || setupObj != null) + { + // NTLMSSP has two stages, if there is a stored setup object then indicate more processing + // required + + if ( sess.hasSetupObject()) + respPkt.setLongErrorCode( SMBStatus.NTMoreProcessingRequired); + else + respPkt.setLongErrorCode( SMBStatus.NTSuccess); + + respPkt.setParameterCount(4); + respPkt.setParameter(0, 0xFF); // No chained response + respPkt.setParameter(1, 0); // Offset to chained response + + respPkt.setParameter(2, 0); // Action + respPkt.setParameter(3, respLen); + } + else + { + // Build a completed session setup response + + respPkt.setLongErrorCode( SMBStatus.NTSuccess); + + // Build the session setup response SMB + + respPkt.setParameterCount(12); + respPkt.setParameter(0, 0xFF); // No chained response + respPkt.setParameter(1, 0); // Offset to chained response + + respPkt.setParameter(2, SMBSrvSession.DefaultBufferSize); + respPkt.setParameter(3, SMBSrvSession.NTMaxMultiplexed); + respPkt.setParameter(4, 0); // virtual circuit number + respPkt.setParameterLong(5, 0); // session key + respPkt.setParameter(7, respLen); + // security blob length + respPkt.setParameterLong(8, 0); // reserved + respPkt.setParameterLong(10, getServerCapabilities()); + } + + // Common session setup response + + respPkt.setCommand( reqPkt.getCommand()); + respPkt.setByteCount(0); + + respPkt.setTreeId(0); + respPkt.setUserId(0); + + // Set the various flags + + int flags = respPkt.getFlags(); + flags &= ~SMBSrvPacket.FLG_CASELESS; + respPkt.setFlags(flags); + + int flags2 = SMBSrvPacket.FLG2_LONGFILENAMES + SMBSrvPacket.FLG2_EXTENDEDSECURITY + SMBSrvPacket.FLG2_LONGERRORCODE; + if ( isUni) + flags2 += SMBSrvPacket.FLG2_UNICODE; + respPkt.setFlags2( flags2); + + // Pack the security blob + + int pos = respPkt.getByteOffset(); + buf = respPkt.getBuffer(); + + if ( respBlob != null) + { + System.arraycopy( respBlob, 0, buf, pos, respBlob.length); + pos += respBlob.length; + } + + // Pack the OS, dialect and domain name strings + + if ( isUni) + pos = DataPacker.wordAlign(pos); + + pos = DataPacker.putString("Java", buf, pos, true, isUni); + pos = DataPacker.putString("Alfresco CIFS Server " + sess.getServer().isVersion(), buf, pos, true, isUni); + pos = DataPacker.putString(sess.getServer().getConfiguration().getDomainName(), buf, pos, true, isUni); + + respPkt.setByteCount(pos - respPkt.getByteOffset()); + } + + /** + * Process an NTLMSSP security blob + * + * @param sess SMBSrvSession + * @param client ClientInfo + * @param secbuf byte[] + * @param secpos int + * @param seclen int + * @param unicode boolean + * @exception SMBSrvException + */ + private final byte[] doNtlmsspSessionSetup( SMBSrvSession sess, ClientInfo client, + byte[] secbuf, int secpos, int seclen, boolean unicode) throws SMBSrvException + { + // Determine the NTLmSSP message type + + int msgType = NTLMMessage.isNTLMType( secbuf, secpos); + byte[] respBlob = null; + + if ( msgType == -1) + { + // DEBUG + + if ( logger.isDebugEnabled()) + { + logger.debug("Invalid NTLMSSP token received"); + logger.debug(" Token=" + HexDump.hexString( secbuf, secpos, seclen, " ")); + } + + // Return a logon failure status + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // Check for a type 1 NTLMSSP message + + else if ( msgType == NTLM.Type1) + { + // Create the type 1 NTLM message from the token + + Type1NTLMMessage type1Msg = new Type1NTLMMessage( secbuf, secpos, seclen); + + // Build the type 2 NTLM response message + // + // Get the flags from the client request and mask out unsupported features + + int ntlmFlags = type1Msg.getFlags() & NTLM_FLAGS; + + // Generate a challenge for the response + + NTLanManAuthContext ntlmCtx = 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)); + 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( 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() == false || sess.getSetupObject() instanceof Type2NTLMMessage == false) + { + // Clear the setup object + + sess.setSetupObject( null); + + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // Determine if the client sent us NTLMv1 or NTLMv2 + + if ( type3Msg.hasFlag( NTLM.Flag128Bit) && type3Msg.hasFlag( NTLM.FlagNTLM2Key)) + { + // 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() && sess.getSetupObject() 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(ex); + + // Return a logon failure status + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // 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()) + 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(ex); + + // Return a logon failure status + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // 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()) + 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 + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + else + { + // Unknown SPNEGO token type + + logger.error( "Unknown SPNEGO token type"); + + // Return a logon failure status + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // Generate the NegTokenTarg blob + + byte[] respBlob = null; + + try + { + // Generate the response blob + + respBlob = negTarg.encode(); + } + catch ( IOException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Failed to encode NegTokenTarg", ex); + + // Failed to build response blob + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // 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 + { + // 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; + + // Create the NegTokenTarg response blob + + negTokenTarg = new NegTokenTarg( SPNEGO.AcceptCompleted, OID.KERBEROS5, krbDetails.getResponseToken()); + + // Setup the Acegi authenticated user + + m_authComponent.setCurrentUser( krbDetails.getUserName()); + + // 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); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Logged on using Kerberos"); + } + } + catch (Exception ex) + { + // Log the error + + logger.error(ex); + + // Return a logon failure status + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // 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 + + logger.warn("NTLMv1 not accepted, client " + sess.getRemoteName()); + + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + + // Get the type 2 message that contains the challenge sent to the client + + Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject(); + sess.setSetupObject( null); + + // Check if we are using local MD4 password hashes or passthru authentication + + if ( m_authComponent.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 md4hash = m_authComponent.getMD4HashedPassword(userName); + + 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 + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + // Setup the Acegi authenticated user + + m_authComponent.setCurrentUser( userName); + + // 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + /** + * 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.DOSAccessDenied, SMBStatus.ErrDos); + } + + // Check if we are using local MD4 password hashes or passthru authentication + + if ( m_authComponent.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 md4hash = m_authComponent.getMD4HashedPassword(client.getUserName()); + + 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) + { + } + + // 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 + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + // Setup the Acegi authenticated user + + m_authComponent.setCurrentUser( client.getUserName()); + + // 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + /** + * 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(); + sess.setSetupObject( null); + + // Check if we are using local MD4 password hashes or passthru authentication + + if ( m_authComponent.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 md4hash = m_authComponent.getMD4HashedPassword(userName); + + 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 + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + // Setup the Acegi authenticated user + + m_authComponent.setCurrentUser( userName); + + // 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) + { + // Log the error + + logger.error(ex); + + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + /** + * 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 ( m_authComponent.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 md4hash = m_authComponent.getMD4HashedPassword(client.getUserName()); + + 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) + { + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + // Setup the Acegi authenticated user + + m_authComponent.setCurrentUser( client.getUserName()); + + // 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 + + logger.error(ex); + + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + /** + * 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(); + sess.setSetupObject( null); + + // Check if we are using local MD4 password hashes or passthru authentication + + if ( m_authComponent.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 md4hash = m_authComponent.getMD4HashedPassword(userName); + + 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) + { + // Log the error + + 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) + { + // Return a logon failure + + throw new SMBSrvException( SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + // Setup the Acegi authenticated user + + m_authComponent.setCurrentUser( userName); + + // 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + 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.DOSAccessDenied, SMBStatus.ErrDos); + } + } + + /** + * Perform a hashed password logon using either NTLMv1 or NTLMv2 + * + * @param sess SMBSrvSession + * @param reqPkt SMBSrvPacket + * @param respPkt SMBSrvPacket + * @exception SMBSrvException + */ + private final void doHashedPasswordLogon( SMBSrvSession sess, SMBSrvPacket reqPkt, SMBSrvPacket respPkt) + throws SMBSrvException + { + // Check that the received packet looks like a valid NT session setup andX request + + if (reqPkt.checkPacketIsValid(13, 0) == false) + { + throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv); + } + + // 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.SRVNonSpecificError, SMBStatus.ErrSrv); + } + + // 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.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 (logger.isDebugEnabled() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + { + 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 ClientInfo(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() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) + logger.debug("User " + user + ", logged on as guest"); + } + + // 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); + } + + // 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 + + respPkt.setParameterCount(3); + respPkt.setParameter(0, 0); // No chained response + respPkt.setParameter(1, 0); // Offset to chained response + respPkt.setParameter(2, isGuest ? 1 : 0); + respPkt.setByteCount(0); + + respPkt.setTreeId(0); + respPkt.setUserId(0); + + // Set the various flags + + int flags = respPkt.getFlags(); + flags &= ~SMBSrvPacket.FLG_CASELESS; + respPkt.setFlags(flags); + + int flags2 = SMBSrvPacket.FLG2_LONGFILENAMES; + if (isUni) + flags2 += SMBSrvPacket.FLG2_UNICODE; + respPkt.setFlags2(flags2); + + // Pack the OS, dialect and domain name strings. + + int pos = respPkt.getByteOffset(); + buf = respPkt.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.getServer().getConfiguration().getDomainName(), buf, pos, true, isUni); + + respPkt.setByteCount(pos - respPkt.getByteOffset()); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/LocalAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/LocalAuthenticator.java deleted file mode 100644 index 2a7fc82695..0000000000 --- a/source/java/org/alfresco/filesys/server/auth/LocalAuthenticator.java +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.auth; - -import java.util.Random; - -import org.alfresco.config.ConfigElement; -import org.alfresco.filesys.server.SrvSession; -import org.alfresco.filesys.server.config.InvalidConfigurationException; -import org.alfresco.filesys.server.config.ServerConfiguration; -import org.alfresco.filesys.server.core.ShareType; -import org.alfresco.filesys.server.core.SharedDevice; -import org.alfresco.filesys.smb.server.SMBSrvSession; -import org.alfresco.filesys.util.DataPacker; - -/** - *

- * Local Authenticator Class. - *

- * The local authenticator implementation enables user level security mode and uses the user account - * list that is part of the server configuration to determine if a user is allowed to access the - * server/share. - *

- * Note: Switching off encrypted password support will cause later NT4 service pack releases and - * Win2000 to refuse to connect to the server without a registry update on the client. - */ -public class LocalAuthenticator extends SrvAuthenticator -{ - - // Random number generator used to generate challenge keys - - private Random m_random = new Random(System.currentTimeMillis()); - - // Server configuration - - private ServerConfiguration m_config; - - /** - * Local Authenticator Constructor - *

- * Default to user mode security with encrypted password support. - */ - public LocalAuthenticator() - { - setAccessMode(SrvAuthenticator.USER_MODE); - setEncryptedPasswords(true); - } - - /** - * Authenticate the connection to a share - * - * @param client ClienInfo - * @param share SharedDevice - * @param pwd Share level password. - * @param sess Server session - * @return Authentication status. - */ - public int authenticateShareConnect(ClientInfo client, SharedDevice share, String pwd, SrvSession sess) - { - - // If the server is in share mode security allow the user access - - if (this.getAccessMode() == SHARE_MODE) - return SrvAuthenticator.Writeable; - - // Check if the IPC$ share is being accessed - - if (share.getType() == ShareType.ADMINPIPE) - return SrvAuthenticator.Writeable; - - // Check if the user is allowed to access the specified shared device - // - // If a user does not have access to the requested share the connection will still be - // allowed - // but any attempts to access files or search directories will result in a 'no access - // rights' - // error being returned to the client. - - UserAccount user = null; - if (client != null) - user = getUserDetails(client.getUserName()); - - if (user == null) - { - - // Check if the guest account is enabled - - return allowGuest() ? SrvAuthenticator.Writeable : SrvAuthenticator.NoAccess; - } - else if (user.hasShare(share.getName()) == false) - return SrvAuthenticator.NoAccess; - - // Allow user to access this share - - return SrvAuthenticator.Writeable; - } - - /** - * Authenticate a user - * - * @param client Client information - * @param sess Server session - * @param alg Encryption algorithm - */ - public int authenticateUser(ClientInfo client, SrvSession sess, int alg) - { - - // Check if the user exists in the user list - - UserAccount userAcc = getUserDetails(client.getUserName()); - if (userAcc != null) - { - - // Validate the password - - boolean authSts = false; - - if (client.getPassword() != null) - { - - // Validate using the Unicode password - - authSts = validatePassword(userAcc.getPassword(), client.getPassword(), sess.getChallengeKey(), alg); - } - else if (client.hasANSIPassword()) - { - - // Validate using the ANSI password with the LanMan encryption - - authSts = validatePassword(userAcc.getPassword(), client.getANSIPassword(), sess.getChallengeKey(), - SrvAuthenticator.LANMAN); - } - - // Return the authentication status - - return authSts == true ? SrvAuthenticator.AUTH_ALLOW : SrvAuthenticator.AUTH_BADPASSWORD; - } - - // Check if this is an SMB/CIFS null session logon. - // - // The null session will only be allowed to connect to the IPC$ named pipe share. - - if (client.isNullSession() && sess instanceof SMBSrvSession) - return SrvAuthenticator.AUTH_ALLOW; - - // Unknown user - - return allowGuest() ? SrvAuthenticator.AUTH_GUEST : SrvAuthenticator.AUTH_DISALLOW; - } - - /** - * Generate a challenge key - * - * @param sess SrvSession - * @return byte[] - */ - public byte[] getChallengeKey(SrvSession sess) - { - - // Generate a new challenge key, pack the key and return - - byte[] key = new byte[8]; - - DataPacker.putIntelLong(m_random.nextLong(), key, 0); - return key; - } - - /** - * Search for the requried user account details in the defined user list - * - * @param user String - * @return UserAccount - */ - public UserAccount getUserDetails(String user) - { - - // Get the user account list from the configuration - - UserAccountList userList = m_config.getUserAccounts(); - if (userList == null || userList.numberOfUsers() == 0) - return null; - - // Search for the required user account record - - return userList.findUser(user); - } - - /** - * Initialize the authenticator - * - * @param config ServerConfiguration - * @param params ConfigElement - * @exception InvalidConfigurationException - */ - public void initialize(ServerConfiguration config, ConfigElement params) throws InvalidConfigurationException - { - - // Save the server configuration so we can access the defined user list - - m_config = config; - } -} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/NTLanManAuthContext.java b/source/java/org/alfresco/filesys/server/auth/NTLanManAuthContext.java new file mode 100644 index 0000000000..4e1efa12d7 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/NTLanManAuthContext.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth; + +import java.util.Random; + +import org.alfresco.filesys.util.DataPacker; +import org.alfresco.filesys.util.HexDump; + +/** + * NTLM1/LanMan CIFS Authentication Context Class + * + *

Holds the challenge sent to the client during the negotiate phase that is used to verify the hashed password + * in the session setup phase. + * + * @author gkspencer + */ +public class NTLanManAuthContext extends AuthContext +{ + // Random number generator used to generate challenge + + private static Random m_random = new Random(System.currentTimeMillis()); + + // Challenge sent to client + + private byte[] m_challenge; + + /** + * Class constructor + */ + public NTLanManAuthContext() + { + // Generate a new challenge key, pack the key and return + + m_challenge = new byte[8]; + DataPacker.putIntelLong(m_random.nextLong(), m_challenge, 0); + } + + /** + * Class constructor + * + * @param challenge byte[] + */ + public NTLanManAuthContext( byte[] challenge) + { + m_challenge = challenge; + } + + /** + * Get the challenge + * + * return byte[] + */ + public final byte[] getChallenge() + { + return m_challenge; + } + + /** + * Return the CIFS authentication context as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("[NTLM,Challenge="); + str.append(HexDump.hexString(m_challenge)); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/PasswordEncryptor.java b/source/java/org/alfresco/filesys/server/auth/PasswordEncryptor.java index cde91315a5..7762907b16 100644 --- a/source/java/org/alfresco/filesys/server/auth/PasswordEncryptor.java +++ b/source/java/org/alfresco/filesys/server/auth/PasswordEncryptor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Alfresco, Inc. + * Copyright (C) 2005-2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -24,6 +24,7 @@ import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; +import javax.crypto.Mac; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; @@ -75,6 +76,14 @@ public class PasswordEncryptor // Check if DES is available Cipher.getInstance("DES"); + + // Check if HMAC-MD5 is available + + Mac.getInstance("HMACMD5"); + + // Indicate required algorithms are available + + algOK = true; } catch (NoSuchAlgorithmException ex) { @@ -95,13 +104,15 @@ public class PasswordEncryptor * @param plainPwd Plaintext password string * @param encryptKey byte[] Encryption key * @param alg int Encryption algorithm + * @param userName String + * @param domain String * @return byte[] Encrypted password * @exception NoSuchAlgorithmException If a required encryption algorithm is not available + * @exception InvalidKeyException Key is invalid */ - public byte[] generateEncryptedPassword(String plainPwd, byte[] encryptKey, int alg) - throws NoSuchAlgorithmException + public byte[] generateEncryptedPassword(String plainPwd, byte[] encryptKey, int alg, String userName, String domain) + throws NoSuchAlgorithmException, InvalidKeyException { - // Get the password String pwd = plainPwd; @@ -150,6 +161,41 @@ public class PasswordEncryptor // NTLM v2 encryption case NTLM2: + + // Get the MD4 hash of the plaintext password + + byte[] md4Hash = generateEncryptedPassword( plainPwd, encryptKey, MD4, null, null); + + // HMAC-MD5 the username + domain string using the MD4 hash as the key + + Mac hmacMd5 = Mac.getInstance("HMACMD5"); + SecretKeySpec key = new SecretKeySpec( md4Hash, 0, md4Hash.length, "MD5"); + + hmacMd5.init(key); + + // Build the username + domain string and convert to bytes + + StringBuilder str = new StringBuilder(); + + str.append( userName.toUpperCase()); + str.append( domain.toUpperCase()); + + byte[] dataByts = null; + + try + { + // Convert the string to a byte array + + String dataStr = str.toString(); + dataByts = dataStr.getBytes("UnicodeLittleUnmarked"); + } + catch ( UnsupportedEncodingException ex) + { + } + + // Encrypt the username+domain bytes to generate the NTLMv2 hash + + encPwd = hmacMd5.doFinal( dataByts); break; // MD4 encryption @@ -414,4 +460,46 @@ public class PasswordEncryptor { return P24(p21, c8); } + + /** + * NTLM2 encryption of the MD4 hashed password + * + * @param md4Hash byte[] + * @param userName String + * @param domain String + * @return byte[] + * @exception NoSuchAlgorithmException + */ + public final byte[] doNTLM2Encryption(byte[] md4Hash, String userName, String domain) + throws NoSuchAlgorithmException, InvalidKeyException + { + // Use the MD4 hashed password as the key for HMAC-MD5 + + Mac hmacMd5 = Mac.getInstance("HMACMD5"); + SecretKeySpec key = new SecretKeySpec(md4Hash, 0, md4Hash.length, "MD5"); + + hmacMd5.init( key); + + // Build the data to be encrypted + + StringBuilder str = new StringBuilder(); + + str.append(userName.toUpperCase()); + str.append(domain.toUpperCase()); + + String dataStr = str.toString(); + byte[] dataByts = null; + + try + { + dataByts = dataStr.getBytes("UnicodeLittleUnmarked"); + } + catch ( UnsupportedEncodingException ex) + { + } + + // Encrypt the data + + return hmacMd5.doFinal( dataByts); + } } diff --git a/source/java/org/alfresco/filesys/server/auth/SrvAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/SrvAuthenticator.java deleted file mode 100644 index 29283800fc..0000000000 --- a/source/java/org/alfresco/filesys/server/auth/SrvAuthenticator.java +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.auth; - -import java.security.NoSuchAlgorithmException; -import java.util.Random; - -import javax.transaction.UserTransaction; - -import net.sf.acegisecurity.Authentication; - -import org.alfresco.config.ConfigElement; -import org.alfresco.filesys.server.SrvSession; -import org.alfresco.filesys.server.config.InvalidConfigurationException; -import org.alfresco.filesys.server.config.ServerConfiguration; -import org.alfresco.filesys.server.core.SharedDevice; -import org.alfresco.filesys.server.filesys.DiskDeviceContext; -import org.alfresco.filesys.server.filesys.DiskInterface; -import org.alfresco.filesys.server.filesys.DiskSharedDevice; -import org.alfresco.filesys.server.filesys.SrvDiskInfo; -import org.alfresco.filesys.smb.server.repo.ContentContext; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.MD4PasswordEncoder; -import org.alfresco.repo.security.authentication.MD4PasswordEncoderImpl; -import org.alfresco.repo.security.authentication.NTLMMode; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AuthenticationService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.transaction.TransactionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - *

- * An authenticator is used by the SMB server to authenticate users when in user level access mode - * and authenticate requests to connect to a share when in share level access. - */ -public abstract class SrvAuthenticator -{ - // Logging - - protected static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); - - // Encryption algorithm types - - public static final int LANMAN = PasswordEncryptor.LANMAN; - public static final int NTLM1 = PasswordEncryptor.NTLM1; - public static final int NTLM2 = PasswordEncryptor.NTLM2; - - // Authentication status values - - public static final int AUTH_ALLOW = 0; - public static final int AUTH_GUEST = 0x10000000; - public static final int AUTH_DISALLOW = -1; - public static final int AUTH_BADPASSWORD = -2; - public static final int AUTH_BADUSER = -3; - - // Share access permissions, returned by authenticateShareConnect() - - public static final int NoAccess = 0; - public static final int ReadOnly = 1; - public static final int Writeable = 2; - - // Server access mode - - public static final int SHARE_MODE = 0; - public static final int USER_MODE = 1; - - // Standard encrypted password length - - public static final int STANDARD_PASSWORD_LEN = 24; - - // Default guest user name - - protected static final String GUEST_USERNAME = "guest"; - - // Server access mode - - private int m_accessMode = SHARE_MODE; - - // Use encrypted password - - private boolean m_encryptPwd = false; - - // Password encryption algorithms - - private PasswordEncryptor m_encryptor = new PasswordEncryptor(); - - // Flag to enable/disable the guest account, and control mapping of unknown users to the guest account - - private boolean m_allowGuest; - private boolean m_mapToGuest; - - // Default guest user name - - private String m_guestUserName = GUEST_USERNAME; - - // Random number generator used to generate challenge keys - - protected Random m_random = new Random(System.currentTimeMillis()); - - // Server configuration - - protected ServerConfiguration m_config; - - // Authentication component, used to access internal authentication functions - - protected AuthenticationComponent m_authComponent; - - // MD4 hash decoder - - protected MD4PasswordEncoder m_md4Encoder = new MD4PasswordEncoderImpl(); - - // Various services required to get user information - - protected NodeService m_nodeService; - protected PersonService m_personService; - protected TransactionService m_transactionService; - protected AuthenticationService m_authenticationService; - - /** - * Authenticate a connection to a share. - * - * @param client User/client details from the tree connect request. - * @param share Shared device the client wants to connect to. - * @param pwd Share password. - * @param sess Server session. - * @return int Granted file permission level or disallow status if negative. See the - * FilePermission class. - */ - public int authenticateShareConnect(ClientInfo client, SharedDevice share, String sharePwd, SrvSession sess) - { - // Allow write access - // - // Main authentication is handled by authenticateUser() - - return SrvAuthenticator.Writeable; - } - - /** - * Authenticate a user. A user may be granted full access, guest access or no access. - * - * @param client User/client details from the session setup request. - * @param sess Server session - * @param alg Encryption algorithm - * @return int Access level or disallow status. - */ - public abstract int authenticateUser(ClientInfo client, SrvSession sess, int alg); - - /** - * Return the user account details for the specified user - * - * @param user String - * @return UserAccount - */ - public UserAccount getUserDetails(String user) - { - return null; - } - - /** - * Authenticate a user using a plain text password. - * - * @param client User/client details from the session setup request. - * @param sess Server session - * @return int Access level or disallow status. - * @throws InvalidConfigurationException - */ - public final int authenticateUserPlainText(ClientInfo client, SrvSession sess) - { - - // Get a challenge key - - sess.setChallengeKey(getChallengeKey(sess)); - - if (sess.hasChallengeKey() == false) - return SrvAuthenticator.AUTH_DISALLOW; - - // Get the plain text password - - String textPwd = client.getPasswordAsString(); - if (textPwd == null) - textPwd = client.getANSIPasswordAsString(); - - // Encrypt the password - - byte[] encPwd = generateEncryptedPassword(textPwd, sess.getChallengeKey(), SrvAuthenticator.NTLM1); - client.setPassword(encPwd); - - // Authenticate the user - - return authenticateUser(client, sess, SrvAuthenticator.NTLM1); - } - - /** - * Initialize the authenticator - * - * @param config ServerConfiguration - * @param params ConfigElement - * @exception InvalidConfigurationException - */ - public void initialize(ServerConfiguration config, ConfigElement params) throws InvalidConfigurationException - { - // Save the server configuration so we can access the authentication component - - m_config = config; - - // Check that the required authentication classes are available - - m_authComponent = m_config.getAuthenticationComponent(); - - if ( m_authComponent == null) - throw new InvalidConfigurationException("Authentication component not available"); - - // Get hold of various services - - m_nodeService = config.getNodeService(); - m_personService = config.getPersonService(); - m_transactionService = config.getTransactionService(); - m_authenticationService = config.getAuthenticationService(); - - // Set the guest user name - - setGuestUserName( m_authComponent.getGuestUserName()); - - // Check that the authentication component is the required type for this authenticator - - if ( validateAuthenticationMode() == false) - throw new InvalidConfigurationException("Required authentication mode not available"); - } - - /** - * Validate that the authentication component supports the required mode - * - * @return boolean - */ - protected boolean validateAuthenticationMode() - { - return true; - } - - /** - * Encrypt the plain text password with the specified encryption key using the specified - * encryption algorithm. - * - * @return byte[] - * @param plainPwd java.lang.String - * @param encryptKey byte[] - * @param alg int - */ - protected final byte[] generateEncryptedPassword(String plainPwd, byte[] encryptKey, int alg) - { - - // Use the password encryptor - - byte[] encPwd = null; - - try - { - - // Encrypt the password - - encPwd = m_encryptor.generateEncryptedPassword(plainPwd, encryptKey, alg); - } - catch (NoSuchAlgorithmException ex) - { - } - - // Return the encrypted password - - return encPwd; - } - - /** - * Return the access mode of the server, either SHARE_MODE or USER_MODE. - * - * @return int - */ - public final int getAccessMode() - { - return m_accessMode; - } - - /** - * Get a challenge encryption key, when encrypted passwords are enabled. - * - * @param sess SrvSession - * @return byte[] - */ - public abstract byte[] getChallengeKey(SrvSession sess); - - /** - * Determine if encrypted passwords should be used. - * - * @return boolean - */ - public final boolean hasEncryptPasswords() - { - return m_encryptPwd; - } - - /** - * Determine if guest access is allowed - * - * @return boolean - */ - public final boolean allowGuest() - { - return m_allowGuest; - } - - /** - * Return the guest user name - * - * @return String - */ - public final String getGuestUserName() - { - return m_guestUserName; - } - - /** - * Determine if unknown users should be mapped to the guest account - * - * @return boolean - */ - public final boolean mapUnknownUserToGuest() - { - return m_mapToGuest; - } - - /** - * Set the access mode of the server. - * - * @param mode Either SHARE_MODE or USER_MODE. - */ - public final void setAccessMode(int mode) - { - m_accessMode = mode; - } - - /** - * Set/clear the encrypted passwords flag. - * - * @param encFlag Encrypt passwords if true, use plain text passwords if false. - */ - public final void setEncryptedPasswords(boolean encFlag) - { - m_encryptPwd = encFlag; - } - - /** - * Enable/disable the guest account - * - * @param ena Enable the guest account if true, only allow defined user accounts access if false - */ - public final void setAllowGuest(boolean ena) - { - m_allowGuest = ena; - } - - /** - * Set the guest user name - * - * @param guest String - */ - public final void setGuestUserName( String guest) - { - m_guestUserName = guest; - } - - /** - * Enable/disable mapping of unknown users to the guest account - * - * @param ena Enable mapping of unknown users to the guest if true - */ - public final void setMapToGuest( boolean ena) - { - m_mapToGuest = ena; - } - - /** - * Close the authenticator, perform any cleanup - */ - public void closeAuthenticator() - { - // Override if cleanup required - } - - /** - * Validate a password by encrypting the plain text password using the specified encryption key - * and encryption algorithm. - * - * @return boolean - * @param plainPwd java.lang.String - * @param encryptedPwd java.lang.String - * @param encryptKey byte[] - * @param alg int - */ - protected final boolean validatePassword(String plainPwd, byte[] encryptedPwd, byte[] encryptKey, int alg) - { - - // Generate an encrypted version of the plain text password - - byte[] encPwd = generateEncryptedPassword(plainPwd != null ? plainPwd : "", encryptKey, alg); - - // Compare the generated password with the received password - - if (encPwd != null && encryptedPwd != null && encPwd.length == STANDARD_PASSWORD_LEN - && encryptedPwd.length == STANDARD_PASSWORD_LEN) - { - - // Compare the password arrays - - for (int i = 0; i < STANDARD_PASSWORD_LEN; i++) - if (encPwd[i] != encryptedPwd[i]) - return false; - - // Password is valid - - return true; - } - - // User or password is invalid - - return false; - } - - /** - * Convert the password string to a byte array - * - * @param pwd String - * @return byte[] - */ - - protected final byte[] convertPassword(String pwd) - { - - // Create a padded/truncated 14 character string - - StringBuffer p14str = new StringBuffer(); - p14str.append(pwd); - if (p14str.length() > 14) - p14str.setLength(14); - else - { - while (p14str.length() < 14) - p14str.append((char) 0x00); - } - - // Convert the P14 string to an array of bytes. Allocate the return 16 byte array. - - return p14str.toString().getBytes(); - } - - /** - * Return the password encryptor - * - * @return PasswordEncryptor - */ - protected final PasswordEncryptor getEncryptor() - { - return m_encryptor; - } - - /** - * Return the authentication status as a string - * - * @param sts int - * @return String - */ - protected final String getStatusAsString(int sts) - { - String str = null; - - switch ( sts) - { - case AUTH_ALLOW: - str = "Allow"; - break; - case AUTH_DISALLOW: - str = "Disallow"; - break; - case AUTH_GUEST: - str = "Guest"; - break; - case AUTH_BADPASSWORD: - str = "BadPassword"; - break; - case AUTH_BADUSER: - str = "BadUser"; - break; - } - - return str; - } - - /** - * Logon using the guest user account - * - * @param client ClientInfo - * @param sess SrvSession - */ - protected final void doGuestLogon( ClientInfo client, SrvSession sess) - { - // Get a guest authentication token - - m_authenticationService.authenticateAsGuest(); - Authentication authToken = m_authComponent.getCurrentAuthentication(); - - client.setAuthenticationToken( authToken); - - // Set the home folder for the guest user - - client.setUserName( getGuestUserName()); - getHomeFolderForUser( client); - - // Mark the client as being a guest logon - - client.setGuest( true); - - // Create a dynamic share for the guest user - // Create the disk driver and context - - DiskInterface diskDrv = m_config.getDiskInterface(); - DiskDeviceContext diskCtx = new ContentContext("", "", client.getHomeFolder()); - - // Default the filesystem to look like an 80Gb sized disk with 90% free space - - diskCtx.setDiskInformation(new SrvDiskInfo(2560, 64, 512, 2304)); - - // Create a temporary shared device for the users home directory - - sess.addDynamicShare( new DiskSharedDevice( client.getUserName(), diskDrv, diskCtx, SharedDevice.Temporary)); - } - - /** - * Get the home folder for the user - * - * @param client ClientInfo - */ - protected final void getHomeFolderForUser(ClientInfo client) - { - // Get the home folder for the user - - UserTransaction tx = m_transactionService.getUserTransaction(); - NodeRef homeSpaceRef = null; - - try - { - tx.begin(); - homeSpaceRef = (NodeRef) m_nodeService.getProperty(m_personService.getPerson(client.getUserName()), - ContentModel.PROP_HOMEFOLDER); - client.setHomeFolder( homeSpaceRef); - tx.commit(); - } - catch (Throwable ex) - { - try - { - tx.rollback(); - } - catch (Throwable ex2) - { - logger.error("Failed to rollback transaction", ex2); - } - - // Re-throw the exception - if (ex instanceof RuntimeException) - { - throw (RuntimeException) ex; - } - else - { - throw new RuntimeException("Error during execution of transaction.", ex); - } - } - } - -} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/UserAccount.java b/source/java/org/alfresco/filesys/server/auth/UserAccount.java deleted file mode 100644 index 0171a68e2e..0000000000 --- a/source/java/org/alfresco/filesys/server/auth/UserAccount.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.auth; - -import org.alfresco.filesys.util.StringList; - -/** - * User Account Class - *

- * Holds the details of a user account on the server. - */ -public class UserAccount -{ - - // User name and password - - private String m_userName; - private String m_password; - - // Real user name and comment - - private String m_realName; - private String m_comment; - - // List of shares this user is allowed to use - - private StringList m_shares; - - // Administrator flag - - private boolean m_admin; - - // Home directory - - private String m_homeDir; - - /** - * Default constructor - */ - public UserAccount() - { - super(); - } - - /** - * Create a user with the specified name and password. - * - * @param user String - * @param pwd String - */ - public UserAccount(String user, String pwd) - { - setUserName(user); - setPassword(pwd); - } - - /** - * Add the specified share to the list of allowed shares for this user. - * - * @param shr java.lang.String - */ - public final void addShare(String shr) - { - if (m_shares == null) - m_shares = new StringList(); - m_shares.addString(shr); - } - - /** - * Determine if this user is allowed to access the specified share. - * - * @return boolean - * @param shr java.lang.String - */ - public final boolean allowsShare(String shr) - { - if (m_shares == null) - return true; - else if (m_shares.containsString(shr)) - return true; - return false; - } - - /** - * Check if the user has a home direectory configured - * - * @return boolean - */ - public final boolean hasHomeDirectory() - { - return m_homeDir != null ? true : false; - } - - /** - * Return the home directory for this user - * - * @return String - */ - public final String getHomeDirectory() - { - return m_homeDir; - } - - /** - * Return the password - * - * @return java.lang.String - */ - public final String getPassword() - { - return m_password; - } - - /** - * Return the user name. - * - * @return java.lang.String - */ - public final String getUserName() - { - return m_userName; - } - - /** - * Return the real user name - * - * @return String - */ - public final String getRealName() - { - return m_realName; - } - - /** - * Return the user comment - * - * @return String - */ - public final String getComment() - { - return m_comment; - } - - /** - * Check if the specified share is listed in the users allowed list. - * - * @return boolean - * @param shr java.lang.String - */ - public final boolean hasShare(String shr) - { - if (m_shares != null && m_shares.containsString(shr) == false) - return false; - return true; - } - - /** - * Detemrine if this account is restricted to using certain shares only. - * - * @return boolean - */ - public final boolean hasShareRestrictions() - { - return m_shares == null ? false : true; - } - - /** - * Return the list of shares - * - * @return StringList - */ - public final StringList getShareList() - { - return m_shares; - } - - /** - * Determine if this user in an administrator. - * - * @return boolean - */ - public final boolean isAdministrator() - { - return m_admin; - } - - /** - * Remove all shares from the list of restricted shares. - */ - public final void removeAllShares() - { - m_shares = null; - } - - /** - * Remove the specified share from the list of shares this user is allowed to access. - * - * @param shr java.lang.String - */ - public final void removeShare(String shr) - { - - // Check if the share list has been allocated - - if (m_shares != null) - { - - // Remove the share from the list - - m_shares.removeString(shr); - - // Check if the list is empty - - if (m_shares.numberOfStrings() == 0) - m_shares = null; - } - } - - /** - * Set the administrator flag. - * - * @param admin boolean - */ - public final void setAdministrator(boolean admin) - { - m_admin = admin; - } - - /** - * Set the user home directory - * - * @param home String - */ - public final void setHomeDirectory(String home) - { - m_homeDir = home; - } - - /** - * Set the password for this account. - * - * @param pwd java.lang.String - */ - public final void setPassword(String pwd) - { - m_password = pwd; - } - - /** - * Set the user name. - * - * @param user java.lang.String - */ - public final void setUserName(String user) - { - m_userName = user; - } - - /** - * Set the real user name - * - * @param name String - */ - public final void setRealName(String name) - { - m_realName = name; - } - - /** - * Set the comment - * - * @param comment String - */ - public final void setComment(String comment) - { - m_comment = comment; - } - - /** - * Return the user account as a string. - * - * @return java.lang.String - */ - public String toString() - { - StringBuffer str = new StringBuffer(); - - str.append("["); - str.append(getUserName()); - str.append(":"); - str.append(getPassword()); - - if (isAdministrator()) - str.append("(ADMIN)"); - - str.append(",Real="); - - str.append(getRealName()); - str.append(",Comment="); - str.append(getComment()); - str.append(",Allow="); - - if (m_shares == null) - str.append(""); - else - str.append(m_shares); - str.append("]"); - - str.append(",Home="); - if (hasHomeDirectory()) - str.append(getHomeDirectory()); - else - str.append("None"); - - return str.toString(); - } -} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/UserAccountList.java b/source/java/org/alfresco/filesys/server/auth/UserAccountList.java deleted file mode 100644 index 4d1d93b7b6..0000000000 --- a/source/java/org/alfresco/filesys/server/auth/UserAccountList.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.auth; - -import java.util.Vector; - -/** - * User Account List Class - */ -public class UserAccountList -{ - - // User account list - - private Vector m_users; - - /** - * Create a user account list. - */ - public UserAccountList() - { - m_users = new Vector(); - } - - /** - * Add a user to the list of accounts. - * - * @param user UserAccount - */ - public final void addUser(UserAccount user) - { - - // Check if the user exists on the list - - removeUser(user); - m_users.add(user); - } - - /** - * Find the required user account details. - * - * @param user java.lang.String - * @return UserAccount - */ - public final UserAccount findUser(String user) - { - - // Search for the specified user account - - for (int i = 0; i < m_users.size(); i++) - { - UserAccount acc = m_users.get(i); - if (acc.getUserName().equalsIgnoreCase(user)) - return acc; - } - - // User not found - - return null; - } - - /** - * Determine if the specified user account exists in the list. - * - * @return boolean - * @param user java.lang.String - */ - public final boolean hasUser(String user) - { - - // Search for the specified user account - - for (int i = 0; i < m_users.size(); i++) - { - UserAccount acc = m_users.get(i); - if (acc.getUserName().compareTo(user) == 0) - return true; - } - - // User not found - - return false; - } - - /** - * Return the specified user account details - * - * @param idx int - * @return UserAccount - */ - public final UserAccount getUserAt(int idx) - { - if (idx >= m_users.size()) - return null; - return m_users.get(idx); - } - - /** - * Return the number of defined user accounts. - * - * @return int - */ - public final int numberOfUsers() - { - return m_users.size(); - } - - /** - * Remove all user accounts from the list. - */ - public final void removeAllUsers() - { - m_users.removeAllElements(); - } - - /** - * Remvoe the specified user account from the list. - * - * @param userAcc UserAccount - */ - public final void removeUser(UserAccount userAcc) - { - - // Search for the specified user account - - for (int i = 0; i < m_users.size(); i++) - { - UserAccount acc = m_users.get(i); - if (acc.getUserName().compareTo(userAcc.getUserName()) == 0) - { - m_users.remove(i); - return; - } - } - } - - /** - * Remvoe the specified user account from the list. - * - * @param user java.lang.String - */ - public final void removeUser(String user) - { - - // Search for the specified user account - - for (int i = 0; i < m_users.size(); i++) - { - UserAccount acc = m_users.get(i); - if (acc.getUserName().compareTo(user) == 0) - { - m_users.remove(i); - return; - } - } - } - - /** - * Return the user account list as a string. - * - * @return java.lang.String - */ - public String toString() - { - StringBuffer str = new StringBuffer(); - - str.append("["); - str.append(m_users.size()); - str.append(":"); - - for (int i = 0; i < m_users.size(); i++) - { - UserAccount acc = m_users.get(i); - str.append(acc.getUserName()); - str.append(","); - } - str.append("]"); - - return str.toString(); - } -} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/kerberos/KerberosDetails.java b/source/java/org/alfresco/filesys/server/auth/kerberos/KerberosDetails.java new file mode 100644 index 0000000000..8b8bfcc059 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/kerberos/KerberosDetails.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth.kerberos; + +import org.ietf.jgss.GSSName; + +/** + * Kerberos Details Class + * + *

Holds the Kerberos response token and session details about the user. + * + * @author gkspencer + */ +public class KerberosDetails +{ + // Source and target details + + private String m_krbSource; + private String m_krbTarget; + + // Kerberos response token + + private byte[] m_krbResponse; + + /** + * Class constructor + * + * @param source GSSName + * @param target GSSName + * @param response byte[] + */ + public KerberosDetails(GSSName source, GSSName target, byte[] response) + { + m_krbSource = source.toString(); + m_krbTarget = target.toString(); + + m_krbResponse = response; + } + + /** + * Return the context initiator for the Kerberos authentication + * + * @return String + */ + public final String getSourceName() + { + return m_krbSource; + } + + /** + * Return the context acceptor for the Kerberos authentication + * + * @return String + */ + public final String getTargetName() + { + return m_krbTarget; + } + + /** + * Return the Kerberos response token + * + * @return byte[] + */ + public final byte[] getResponseToken() + { + return m_krbResponse; + } + + /** + * Parse the source name to return the user name part only + * + * @return String + */ + public final String getUserName() + { + String userName = m_krbSource; + + if ( m_krbSource != null) + { + int pos = m_krbSource.indexOf( '@'); + if ( pos != -1) + { + userName = m_krbSource.substring(0, pos); + } + } + + return userName; + } + + /** + * Return the response token length + * + * @return int + */ + public final int getResponseLength() + { + return m_krbResponse != null ? m_krbResponse.length : 0; + } + + /** + * Return the Kerberos authentication details as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("[Source="); + str.append(getSourceName()); + str.append(",Target="); + str.append(getTargetName()); + str.append(":Response="); + str.append(getResponseLength()); + str.append(" bytes]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/kerberos/SessionSetupPrivilegedAction.java b/source/java/org/alfresco/filesys/server/auth/kerberos/SessionSetupPrivilegedAction.java new file mode 100644 index 0000000000..e2077a6f51 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/kerberos/SessionSetupPrivilegedAction.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth.kerberos; + +import java.security.PrivilegedAction; + +import org.alfresco.filesys.server.auth.spnego.OID; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; + +/** + * Session Setup Privileged Action Class + * + *

Handle the processing of a received SPNEGO packet in the context of the CIFS server. + * + * @author gkspencer + */ +public class SessionSetupPrivilegedAction implements PrivilegedAction +{ + // Received security blob details + + private byte[] m_secBlob; + private int m_secOffset; + private int m_secLen; + + // CIFS server account name + + private String m_accountName; + + /** + * Class constructor + * + * @param accountName String + * @param secBlob byte[] + */ + public SessionSetupPrivilegedAction ( String accountName, byte[] secBlob) + { + m_accountName = accountName; + + m_secBlob = secBlob; + m_secOffset = 0; + m_secLen = secBlob.length; + } + + /** + * Class constructor + * + * @param accountName String + * @param secBlob byte[] + * @param secOffset int + * @param secLen int + */ + public SessionSetupPrivilegedAction ( String accountName, byte[] secBlob, int secOffset, int secLen) + { + m_accountName = accountName; + + m_secBlob = secBlob; + m_secOffset = secOffset; + m_secLen = secLen; + } + + /** + * Run the privileged action + */ + public Object run() + { + KerberosDetails krbDetails = null; + + try + { + GSSManager gssManager = GSSManager.getInstance(); + GSSName serverGSSName = gssManager.createName(m_accountName, GSSName.NT_USER_NAME); + GSSCredential serverGSSCreds = gssManager.createCredential( serverGSSName, GSSCredential.INDEFINITE_LIFETIME, + OID.KERBEROS5, GSSCredential.ACCEPT_ONLY); + + GSSContext serverGSSContext = gssManager.createContext( serverGSSCreds); + + // Accept the incoming security blob and generate the response blob + + byte[] respBlob = serverGSSContext.acceptSecContext( m_secBlob, m_secOffset, m_secLen); + + // Create the Kerberos response details + + krbDetails = new KerberosDetails( serverGSSContext.getSrcName(), serverGSSContext.getTargName(), respBlob); + } + catch (GSSException ex) + { + System.out.println("GSSException: " + ex.getMajorString()); + System.out.println(" " + ex.getMessage()); + } + + // Return the Kerberos response + + return krbDetails; + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java index 3324f65f0c..9a65f8fea6 100644 --- a/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java @@ -20,8 +20,9 @@ import java.security.NoSuchAlgorithmException; import net.sf.acegisecurity.Authentication; import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.CifsAuthenticator; import org.alfresco.filesys.server.auth.ClientInfo; -import org.alfresco.filesys.server.auth.SrvAuthenticator; +import org.alfresco.filesys.server.auth.NTLanManAuthContext; import org.alfresco.filesys.smb.server.SMBSrvSession; import org.alfresco.filesys.util.DataPacker; import org.alfresco.repo.security.authentication.NTLMMode; @@ -38,7 +39,7 @@ import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken; * * @author GKSpencer */ -public class AlfrescoAuthenticator extends SrvAuthenticator +public class AlfrescoAuthenticator extends CifsAuthenticator { /** * Default Constructor @@ -47,8 +48,6 @@ public class AlfrescoAuthenticator extends SrvAuthenticator */ public AlfrescoAuthenticator() { - setAccessMode(SrvAuthenticator.USER_MODE); - setEncryptedPasswords(true); } /** @@ -86,7 +85,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator if ( logger.isDebugEnabled()) logger.debug("Null CIFS logon allowed"); - return SrvAuthenticator.AUTH_ALLOW; + return CifsAuthenticator.AUTH_ALLOW; } // Check if the client is already authenticated, and it is not a null logon @@ -196,12 +195,13 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Check if the client is already authenticated, and it is not a null logon - if ( sess.hasClientInformation() && sess.getClientInformation().getAuthenticationToken() != null && + if ( sess.hasAuthenticationContext() && sess.hasAuthenticationToken() && sess.getClientInformation().getLogonType() != ClientInfo.LogonNull) { // Return the previous challenge, user is already authenticated - - key = sess.getChallengeKey(); + + NTLanManAuthContext authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); + key = authCtx.getChallenge(); // DEBUG @@ -260,7 +260,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Check if the client has supplied an NTLM hashed password, if not then do not allow access if ( client.getPassword() == null) - return SrvAuthenticator.AUTH_BADPASSWORD; + return CifsAuthenticator.AUTH_BADPASSWORD; try { @@ -270,21 +270,30 @@ public class AlfrescoAuthenticator extends SrvAuthenticator byte[] md4byts = m_md4Encoder.decodeHash(md4hash); System.arraycopy(md4byts, 0, p21, 0, 16); + // Get the challenge that was sent to the client + + NTLanManAuthContext authCtx = null; + + if ( sess.hasAuthenticationContext() && sess.getAuthenticationContext() instanceof NTLanManAuthContext) + authCtx = (NTLanManAuthContext) sess.getAuthenticationContext(); + else + return CifsAuthenticator.AUTH_DISALLOW; + // Generate the local hash of the password using the same challenge - byte[] localHash = getEncryptor().doNTLM1Encryption(p21, sess.getChallengeKey()); + byte[] localHash = getEncryptor().doNTLM1Encryption(p21, authCtx.getChallenge()); // Validate the password byte[] clientHash = client.getPassword(); if ( clientHash == null || clientHash.length != localHash.length) - return SrvAuthenticator.AUTH_BADPASSWORD; + return CifsAuthenticator.AUTH_BADPASSWORD; for ( int i = 0; i < clientHash.length; i++) { if ( clientHash[i] != localHash[i]) - return SrvAuthenticator.AUTH_BADPASSWORD; + return CifsAuthenticator.AUTH_BADPASSWORD; } // Set the current user to be authenticated, save the authentication token @@ -297,7 +306,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Passwords match, grant access - return SrvAuthenticator.AUTH_ALLOW; + return CifsAuthenticator.AUTH_ALLOW; } catch (NoSuchAlgorithmException ex) { @@ -305,7 +314,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Error during password check, do not allow access - return SrvAuthenticator.AUTH_DISALLOW; + return CifsAuthenticator.AUTH_DISALLOW; } // Check if this is an SMB/CIFS null session logon. @@ -313,11 +322,11 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // The null session will only be allowed to connect to the IPC$ named pipe share. if (client.isNullSession() && sess instanceof SMBSrvSession) - return SrvAuthenticator.AUTH_ALLOW; + return CifsAuthenticator.AUTH_ALLOW; // User does not exist, check if guest access is allowed - return allowGuest() ? SrvAuthenticator.AUTH_GUEST : SrvAuthenticator.AUTH_DISALLOW; + return allowGuest() ? CifsAuthenticator.AUTH_GUEST : CifsAuthenticator.AUTH_DISALLOW; } /** @@ -335,11 +344,11 @@ public class AlfrescoAuthenticator extends SrvAuthenticator NTLMPassthruToken authToken = (NTLMPassthruToken) sess.getAuthenticationToken(); if ( authToken == null) - return SrvAuthenticator.AUTH_DISALLOW; + return CifsAuthenticator.AUTH_DISALLOW; // Get the appropriate hashed password for the algorithm - int authSts = SrvAuthenticator.AUTH_DISALLOW; + int authSts = CifsAuthenticator.AUTH_DISALLOW; byte[] hashedPassword = null; if ( alg == NTLM1) @@ -350,7 +359,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator { // Invalid/unsupported algorithm specified - return SrvAuthenticator.AUTH_DISALLOW; + return CifsAuthenticator.AUTH_DISALLOW; } // Set the username and hashed password in the authentication token @@ -379,7 +388,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Allow the user access as a guest - authSts = SrvAuthenticator.AUTH_GUEST; + authSts = CifsAuthenticator.AUTH_GUEST; } } else @@ -387,7 +396,7 @@ public class AlfrescoAuthenticator extends SrvAuthenticator // Allow the user full access to the server - authSts = SrvAuthenticator.AUTH_ALLOW; + authSts = CifsAuthenticator.AUTH_ALLOW; } // Set the current user to be authenticated, save the authentication token diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLM.java b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLM.java index 7c9c833fb0..209accb517 100644 --- a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLM.java +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLM.java @@ -42,14 +42,15 @@ public class NTLM public static final int FlagNegotiateSeal = 0x00000020; public static final int FlagDatagramStyle = 0x00000040; public static final int FlagLanManKey = 0x00000080; + public static final int FlagNegotiateNetware = 0x00000100; public static final int FlagNegotiateNTLM = 0x00000200; public static final int FlagDomainSupplied = 0x00001000; public static final int FlagWorkstationSupplied = 0x00002000; public static final int FlagLocalCall = 0x00004000; public static final int FlagAlwaysSign = 0x00008000; - public static final int FlagTypeDomain = 0x00010000; - public static final int FlagTypeServer = 0x00020000; - public static final int FlagTypeShare = 0x00040000; + public static final int FlagChallengeInit = 0x00010000; + public static final int FlagChallengeAccept = 0x00020000; + public static final int FlagChallengeNonNT = 0x00040000; public static final int FlagNTLM2Key = 0x00080000; public static final int FlagTargetInfo = 0x00800000; public static final int Flag128Bit = 0x20000000; diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessage.java b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessage.java index c67a8ab034..03d36f3fa0 100644 --- a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessage.java +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessage.java @@ -204,7 +204,7 @@ public abstract class NTLMMessage */ protected final int getByteOffset(int offset) { - return getIntValue(m_offset + offset + 4); + return getIntValue(offset + 4); } /** @@ -217,11 +217,11 @@ public abstract class NTLMMessage { // Get the byte block length - int bLen = getShortValue(m_offset + offset); + int bLen = getShortValue(offset); if ( bLen == 0) return null; - int bOff = getIntValue(m_offset + offset + 4); + int bOff = getIntValue(offset + 4); return getRawBytes(bOff, bLen); } diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMv2Blob.java b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMv2Blob.java new file mode 100644 index 0000000000..c07e7ef023 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMv2Blob.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth.ntlm; + +import java.util.Date; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.alfresco.filesys.smb.NTTime; +import org.alfresco.filesys.util.DataPacker; +import org.alfresco.filesys.util.HexDump; + +/** + * NTLMv2 Blob Class + * + *

Contains methods to pack/unpack and calculate the hash of an NTLMv2 blob. + * + * @author gkspencer + */ +public class NTLMv2Blob +{ + // Constants + + public static final int HMAC_LEN = 16; + public static final int CHALLENGE_LEN = 8; + + // Offsets + + public static final int OFFSET_HMAC = 0; + public static final int OFFSET_HEADER = 16; + public static final int OFFSET_RESERVED = 20; + public static final int OFFSET_TIMESTAMP = 24; + public static final int OFFSET_CHALLENGE = 32; + public static final int OFFSET_UNKNOWN = 36; + public static final int OFFSET_TARGETINFO = 40; + + // NTLMv2 blob + + private byte[] m_blob; + private int m_offset; + private int m_len; + + /** + * Class constructor + * + * @param buf byte[] + */ + public NTLMv2Blob(byte[] buf) + { + m_blob = buf; + m_offset = 0; + m_len = m_blob.length; + } + + /** + * Class constructor + * + * @param buf byte[] + * @param offset int + * @param len int + */ + public NTLMv2Blob(byte[] buf, int offset, int len) + { + m_blob = buf; + m_offset = offset; + m_len = len; + } + + /** + * Return the buffer + * + * @return byte[] + */ + public final byte[] getBuffer() + { + return m_blob; + } + + /** + * Return the offset + * + * @return int + */ + public final int getOffset() + { + return m_offset; + } + + /** + * Return the blob length + * + * @return int + */ + public final int getLength() + { + return m_len; + } + + /** + * Return the HMAC from the buffer + * + * @return byte[] + */ + public final byte[] getHMAC() + { + byte[] hmac = new byte[HMAC_LEN]; + System.arraycopy(m_blob, m_offset, hmac, 0, HMAC_LEN); + + return hmac; + } + + /** + * Return the timestamp from the buffer, in NT 64bit time format + * + * @return long + */ + public final long getTimeStamp() + { + return DataPacker.getIntelLong(m_blob, m_offset + OFFSET_TIMESTAMP); + } + + /** + * Return the client challenge + * + * @return byte[] + */ + public final byte[] getClientChallenge() + { + byte[] challenge = new byte[CHALLENGE_LEN]; + System.arraycopy( m_blob, m_offset + OFFSET_CHALLENGE, challenge, 0, CHALLENGE_LEN); + + return challenge; + } + + /** + * Calculate the HMAC of the blob using the specified NTLMv2 hash and challenge + * + * @param challenge byte[] + * @param v2hash byte[] + * @return byte[] + * @exception Exception + */ + public final byte[] calculateHMAC( byte[] challenge, byte[] v2hash) + throws Exception + { + // Create a copy of the NTLMv2 blob with room for the challenge + + byte[] blob = new byte[(m_len - HMAC_LEN) + CHALLENGE_LEN]; + System.arraycopy( challenge, 0, blob, 0, CHALLENGE_LEN); + System.arraycopy( m_blob, m_offset + OFFSET_HEADER, blob, CHALLENGE_LEN, m_len - HMAC_LEN); + + // Generate the HMAC of the blob using the v2 hash as the key + + Mac hmacMd5 = Mac.getInstance( "HMACMD5"); + SecretKeySpec blobKey = new SecretKeySpec( v2hash, 0, v2hash.length, "MD5"); + + hmacMd5.init( blobKey); + return hmacMd5.doFinal( blob); + } + + /** + * Dump the NTLMv2 blob details + */ + public final void Dump() + { + System.out.println("NTLMv2 blob :"); + System.out.println(" HMAC : " + HexDump.hexString( getHMAC())); + System.out.println(" Header : 0x" + Integer.toHexString(DataPacker.getIntelInt( m_blob, m_offset + OFFSET_HEADER))); + System.out.println(" Timestamp : " + new Date(NTTime.toJavaDate( getTimeStamp()))); + System.out.println(" Challenge : " + HexDump.hexString( getClientChallenge())); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/Type3NTLMMessage.java b/source/java/org/alfresco/filesys/server/auth/ntlm/Type3NTLMMessage.java index 0c82a0edd2..e77a71dd2d 100644 --- a/source/java/org/alfresco/filesys/server/auth/ntlm/Type3NTLMMessage.java +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/Type3NTLMMessage.java @@ -92,6 +92,16 @@ public class Type3NTLMMessage extends NTLMMessage return getIntValue(OffsetFlags); } + /** + * Return the length of the LM hash + * + * @return int + */ + public final int getLMHashLength() + { + return getShortValue(OffsetLMResponse); + } + /** * Return the LM password hash * @@ -102,6 +112,16 @@ public class Type3NTLMMessage extends NTLMMessage return getByteValue(OffsetLMResponse); } + /** + * Return the length of the NTLM hash + * + * @return int + */ + public final int getNTLMHashLength() + { + return getShortValue(OffsetNTLMResponse); + } + /** * Return the NTLM password hash * diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/AcegiPassthruAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/passthru/AcegiPassthruAuthenticator.java deleted file mode 100644 index 150bb13a52..0000000000 --- a/source/java/org/alfresco/filesys/server/auth/passthru/AcegiPassthruAuthenticator.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2005-2006 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.filesys.server.auth.passthru; - -import java.util.List; - -import net.sf.acegisecurity.AuthenticationManager; -import net.sf.acegisecurity.providers.ProviderManager; - -import org.alfresco.config.ConfigElement; -import org.alfresco.filesys.server.SrvSession; -import org.alfresco.filesys.server.auth.ClientInfo; -import org.alfresco.filesys.server.auth.SrvAuthenticator; -import org.alfresco.filesys.server.auth.UserAccount; -import org.alfresco.filesys.server.config.InvalidConfigurationException; -import org.alfresco.filesys.server.config.ServerConfiguration; -import org.alfresco.filesys.server.core.SharedDevice; -import org.alfresco.repo.security.authentication.ntlm.NTLMAuthenticationProvider; -import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - *

Passthru authenticator implementation that uses the Acegi NTLM passthru authentication provider - * - * @author GKSpencer - */ -public class AcegiPassthruAuthenticator extends SrvAuthenticator -{ - // Debug logging - - private static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); - - // Constants - // - // Default authentication manager bean name - - private static final String DefaultAuthManagerName = "authenticationManager"; - - // Acegi authentication manager - - private AuthenticationManager m_authMgr; - - /** - * Default constructor - */ - public AcegiPassthruAuthenticator() - { - setAccessMode(SrvAuthenticator.USER_MODE); - setEncryptedPasswords(true); - } - - /** - * Authenticate the connection to a particular share, called when the SMB server is in share - * security mode - * - * @param client ClientInfo - * @param share SharedDevice - * @param sharePwd String - * @param sess SrvSession - * @return int - */ - public int authenticateShareConnect(ClientInfo client, SharedDevice share, String sharePwd, SrvSession sess) - { - return SrvAuthenticator.Writeable; - } - - /** - * Authenticate a session setup by a user - * - * @param client ClientInfo - * @param sess SrvSession - * @param alg int - * @return int - */ - public int authenticateUser(ClientInfo client, SrvSession sess, int alg) - { - // Get the authentication token for the session - - NTLMPassthruToken authToken = (NTLMPassthruToken) sess.getAuthenticationToken(); - - if ( authToken == null) - return SrvAuthenticator.AUTH_DISALLOW; - - // Get the appropriate hashed password for the algorithm - - int authSts = SrvAuthenticator.AUTH_DISALLOW; - byte[] hashedPassword = null; - - if ( alg == NTLM1) - hashedPassword = client.getPassword(); - else if ( alg == LANMAN) - hashedPassword = client.getANSIPassword(); - else - { - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Invalid algorithm specified for user authentication (" + alg + ")"); - - // Invalid/unsupported algorithm specified - - return SrvAuthenticator.AUTH_DISALLOW; - } - - // Set the username and hashed password in the authentication token - - authToken.setUserAndPassword( client.getUserName(), hashedPassword, alg); - - // Authenticate the user - - try - { - // Run the second stage of the passthru authentication - - m_authMgr.authenticate( authToken); - - // Check if the user has been logged on as a guest - - if (authToken.isGuestLogon()) - { - - // Check if the local server allows guest access - - if (allowGuest() == true) - { - - // Allow the user access as a guest - - authSts = SrvAuthenticator.AUTH_GUEST; - - // Debug - - if (logger.isDebugEnabled()) - logger.debug("Acegi passthru authenticate user=" + client.getUserName() + ", GUEST"); - } - } - else - { - - // Allow the user full access to the server - - authSts = SrvAuthenticator.AUTH_ALLOW; - - // Debug - - if (logger.isDebugEnabled()) - logger.debug("Acegi passthru authenticate user=" + client.getUserName() + ", FULL"); - } - } - catch ( Exception ex) - { - // Log the error - - if ( logger.isErrorEnabled()) - logger.error("Logon failure, " + ex.getMessage()); - } - - // Clear the authentication token - - sess.setAuthenticationToken(null); - - // Return the authentication status - - return authSts; - } - - /** - * Get user account details for the specified user - * - * @param user String - * @return UserAccount - */ - public UserAccount getUserDetails(String user) - { - // No user details to return - - return null; - } - - /** - * Get a challenge key for a new session - * - * @param sess SrvSession - * @return byte[] - */ - public byte[] getChallengeKey(SrvSession sess) - { - // Create an authentication token for the session - - NTLMPassthruToken authToken = new NTLMPassthruToken(); - - // Run the first stage of the passthru authentication to get the challenge - - m_authMgr.authenticate( authToken); - - // Save the authentication token for the second stage of the authentication - - sess.setAuthenticationToken(authToken); - - // Debug - - if ( logger.isDebugEnabled()) - logger.debug("Created new passthru token " + authToken); - - // Get the challenge from the token - - if ( authToken.getChallenge() != null) - return authToken.getChallenge().getBytes(); - return null; - } - - /** - * Initialzie the authenticator - * - * @param config ServerConfiguration - * @param params ConfigElement - * @exception InvalidConfigurationException - */ - public void initialize(ServerConfiguration config, ConfigElement params) throws InvalidConfigurationException - { - // Call the base class - - super.initialize(config, params); - - // Check if the configuration has an associated bean factory, if not it looks like we are - // not running inside Spring - - if ( config.getAuthenticationManager() == null) - throw new InvalidConfigurationException("Acegi authentication manager not available"); - - // Passthru authenticator only works in user mode - - if ( getAccessMode() != USER_MODE) - throw new InvalidConfigurationException("Acegi authenticator only works in user mode"); - - // Check if authentication manager is the required type and that the NTLM authentication provider - // is available. - - Object authMgrObj = config.getAuthenticationManager(); - - if ( authMgrObj instanceof ProviderManager) - { - // The required authentication manager is configured, now check if the NTLM provider is configured - - ProviderManager providerManager = (ProviderManager) authMgrObj; - List providerList = providerManager.getProviders(); - - if ( providerList != null) - { - // Check for the NTLM authentication provider - - int i = 0; - boolean foundProvider = false; - - while ( i < providerList.size() && foundProvider == false) - { - if ( providerList.get(i++) instanceof NTLMAuthenticationProvider) - foundProvider = true; - } - - if (foundProvider == false) - throw new InvalidConfigurationException("NTLM authentication provider is not available"); - - // Save the authentication manager - - m_authMgr = (AuthenticationManager) authMgrObj; - } - else - throw new InvalidConfigurationException("No authentication providers available"); - } - else - throw new InvalidConfigurationException("Required authentication manager is not configured"); - } -} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java index f67e9711ad..634c464b77 100644 --- a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java @@ -24,15 +24,15 @@ import org.alfresco.config.ConfigElement; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.server.SessionListener; import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.AuthContext; import org.alfresco.filesys.server.auth.ClientInfo; -import org.alfresco.filesys.server.auth.SrvAuthenticator; -import org.alfresco.filesys.server.auth.UserAccount; +import org.alfresco.filesys.server.auth.CifsAuthenticator; +import org.alfresco.filesys.server.auth.NTLanManAuthContext; import org.alfresco.filesys.server.config.InvalidConfigurationException; import org.alfresco.filesys.server.config.ServerConfiguration; import org.alfresco.filesys.server.core.SharedDevice; import org.alfresco.filesys.smb.server.SMBServer; import org.alfresco.filesys.smb.server.SMBSrvSession; -import org.alfresco.filesys.util.HexDump; import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.repository.NodeRef; import org.apache.commons.logging.Log; @@ -46,7 +46,7 @@ import org.apache.commons.logging.LogFactory; * * @author GKSpencer */ -public class PassthruAuthenticator extends SrvAuthenticator implements SessionListener +public class PassthruAuthenticator extends CifsAuthenticator implements SessionListener { // Debug logging @@ -77,9 +77,6 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi */ public PassthruAuthenticator() { - setAccessMode(SrvAuthenticator.USER_MODE); - setEncryptedPasswords(true); - // Allocate the session table m_sessions = new Hashtable(); @@ -97,7 +94,7 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi */ public int authenticateShareConnect(ClientInfo client, SharedDevice share, String sharePwd, SrvSession sess) { - return SrvAuthenticator.Writeable; + return CifsAuthenticator.Writeable; } /** @@ -110,18 +107,16 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi */ public int authenticateUser(ClientInfo client, SrvSession sess, int alg) { - // Check if this is an SMB/CIFS null session logon. - // // The null session will only be allowed to connect to the IPC$ named pipe share. - if (client.isNullSession() && sess instanceof SMBSrvSession) + if (client.isNullSession()) { // Debug if ( logger.isDebugEnabled()) logger.debug("Null CIFS logon allowed"); - return SrvAuthenticator.AUTH_ALLOW; + return CifsAuthenticator.AUTH_ALLOW; } // Check if the client is already authenticated, and it is not a null logon @@ -202,7 +197,7 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi // Allow the user access as a guest - authSts = SrvAuthenticator.AUTH_GUEST; + authSts = CifsAuthenticator.AUTH_GUEST; // Debug @@ -255,7 +250,7 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi // Allow the user full access to the server - authSts = SrvAuthenticator.AUTH_ALLOW; + authSts = CifsAuthenticator.AUTH_ALLOW; // Debug @@ -336,35 +331,21 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi } /** - * Get user account details for the specified user + * Return an authentication context for the new session * - * @param user String - * @return UserAccount + * @return AuthContext */ - public UserAccount getUserDetails(String user) - { - - // No user details to return - - return null; - } - - /** - * Get a challenge key for a new session - * - * @param sess SrvSession - * @return byte[] - */ - public byte[] getChallengeKey(SrvSession sess) + public AuthContext getAuthContext( SMBSrvSession sess) { // Check for an SMB session - byte[] chKey = null; + AuthContext authCtx = null; // Check if the client is already authenticated, and it is not a null logon - if ( sess.hasClientInformation() && sess.getClientInformation().getAuthenticationToken() != null && + if ( sess.hasAuthenticationContext() && sess.hasClientInformation() && + sess.getClientInformation().getAuthenticationToken() != null && sess.getClientInformation().getLogonType() != ClientInfo.LogonNull) { // DEBUG @@ -374,24 +355,7 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi // Return the previous challenge, user is already authenticated - return sess.getChallengeKey(); - } - else if (sess instanceof SMBSrvSession) - { - - // Check if the SMB server listener has been initialized - - if (m_server == null) - { - - // Initialize the SMB server session listener so we receive callbacks when sessions - // are opened/closed on the SMB server - - SMBSrvSession smbSess = (SMBSrvSession) sess; - m_server = smbSess.getSMBServer(); - - m_server.addSessionListener(this); - } + return sess.getAuthenticationContext(); } try @@ -410,13 +374,13 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi // Use the challenge key returned from the authentication server - chKey = authSess.getEncryptionKey(); + authCtx = new NTLanManAuthContext( authSess.getEncryptionKey()); + sess.setAuthenticationContext( authCtx); // DEBUG if (logger.isDebugEnabled()) - logger.debug("Passthru sessId=" + authSess.getSessionId() + ", negotiate key=[" - + HexDump.hexString(chKey) + "]"); + logger.debug("Passthru sessId=" + authSess.getSessionId() + ", auth ctx=" + authCtx); } } catch (Exception ex) @@ -427,9 +391,9 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi logger.error("Passthru error getting challenge", ex); } - // Return the challenge key + // Return the authentication context - return chKey; + return authCtx; } /** @@ -563,6 +527,13 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi if (m_passthruServers.getTotalServerCount() == 0) throw new AlfrescoRuntimeException("No valid authentication servers found for passthru"); + + // Install the SMB server listener so we receive callbacks when sessions are + // opened/closed on the SMB server + + SMBServer smbServer = (SMBServer) config.findServer( "SMB"); + if ( smbServer != null) + smbServer.addSessionListener(this); } /** @@ -639,8 +610,7 @@ public class PassthruAuthenticator extends SrvAuthenticator implements SessionLi { // Check if the client information has an empty user name, if so then do not close the - // authentication - // session + // authentication session if (sess.hasClientInformation() && sess.getClientInformation().getUserName() != null && sess.getClientInformation().getUserName().length() > 0) diff --git a/source/java/org/alfresco/filesys/server/auth/spnego/NegTokenInit.java b/source/java/org/alfresco/filesys/server/auth/spnego/NegTokenInit.java new file mode 100644 index 0000000000..a7a61b2d32 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/spnego/NegTokenInit.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth.spnego; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.DERApplicationSpecific; +import org.bouncycastle.asn1.DERBitString; +import org.bouncycastle.asn1.DERGeneralString; +import org.bouncycastle.asn1.DERObject; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DEROutputStream; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.DERTaggedObject; +import org.bouncycastle.asn1.DERTags; +import org.bouncycastle.asn1.DERUnknownTag; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.Oid; + +/** + * NegTokenInit Class + * + *

Contains the details of an SPNEGO NegTokenInit blob for use with CIFS. + * + * @author gkspencer + */ +public class NegTokenInit +{ + // Mechtypes list + + private Oid[] m_mechTypes; + + // Context flags + + private int m_contextFlags = -1; + + // Mechtoken + + private byte[] m_mechToken; + + // MectListMIC principal + + private String m_mecListMICPrincipal; + + /** + * Class constructor for decoding + */ + public NegTokenInit() + { + } + + /** + * Class constructor for encoding + * + * @param mechTypes Oid[] + * @param mechPrinciple String + */ + public NegTokenInit( Oid[] mechTypes, String mechPrinciple) + { + m_mechTypes = mechTypes; + m_mecListMICPrincipal = mechPrinciple; + } + + /** + * Class constructor for encoding + * + * @param mechTypes Vector + * @param mechPrinciple String + */ + public NegTokenInit( Vector mechTypes, String mechPrinciple) + { + // Create the mechTypes array + + m_mechTypes = new Oid[ mechTypes.size()]; + for ( int i = 0; i < mechTypes.size(); i++) + m_mechTypes[i] = mechTypes.get(i); + + m_mecListMICPrincipal = mechPrinciple; + } + + /** + * Return the mechTypes OID list + * + * @return Oid[] + */ + public final Oid[] getOids() + { + return m_mechTypes; + } + + /** + * Return the context flags + * + * @return int + */ + public final int getContextFlags() + { + return m_contextFlags; + } + + /** + * Return the mechToken + * + * @return byte[] + */ + public final byte[] getMechtoken() + { + return m_mechToken; + } + + /** + * Return the mechListMIC principal + * + * @return String + */ + public final String getPrincipal() + { + return m_mecListMICPrincipal; + } + + /** + * Check if the OID list contains the specified OID + * + * @param oid Oid + * @return boolean + */ + public final boolean hasOid( Oid oid) + { + boolean foundOid = false; + + if ( m_mechTypes != null) + { + foundOid = oid.containedIn( m_mechTypes); + } + + return foundOid; + } + + /** + * Return the count of OIDs + * + * @return int + */ + public final int numberOfOids() + { + return m_mechTypes != null ? m_mechTypes.length : 0; + } + + /** + * Return the specified OID + * + * @param idx int + * @return OID + */ + public final Oid getOidAt(int idx) + { + if ( m_mechTypes != null && idx >= 0 && idx < m_mechTypes.length) + return m_mechTypes[idx]; + return null; + } + + /** + * Decode an SPNEGO NegTokenInit blob + * + * @param buf byte[] + * @param off int + * @param len int + * @exception IOException + */ + public void decode(byte[] buf, int off, int len) throws IOException + { + // Create a stream around the security blob + + ByteArrayInputStream bytStream = new ByteArrayInputStream( buf, off, len); + ASN1InputStream asnStream = new ASN1InputStream( bytStream); + + // Read the top level object from the security blob + + DERObject derObj = asnStream.readObject(); + + if ( derObj instanceof DERApplicationSpecific == false) + throw new IOException("Bad blob format (AppSpec)"); + + // Access the application specific contents + + DERApplicationSpecific derApp = (DERApplicationSpecific) derObj; + + ByteArrayInputStream appStream = new ByteArrayInputStream( derApp.getContents()); + ASN1InputStream asnAppStream = new ASN1InputStream( appStream); + + // First object should be an OID, make sure it is the SPNEGO OID + + derObj = asnAppStream.readObject(); + if ( derObj instanceof DERObjectIdentifier == false) + throw new IOException("Bad blob format (SPNEGO OID)"); + + DERObjectIdentifier derOid = (DERObjectIdentifier) derObj; + if ( derOid.getId().equals( OID.ID_SPNEGO) == false) + throw new IOException("Not an SPNEGO blob"); + + // Next object should be a tagged object with a sequence + + derObj = asnAppStream.readObject(); + if ( derObj instanceof DERTaggedObject == false) + throw new IOException("Bad blob format, tagged object missing"); + + DERTaggedObject derTagSeq = (DERTaggedObject) derObj; + if ( derTagSeq.getTagNo() != 0 || derTagSeq.getObject() instanceof DERSequence == false) + throw new IOException("Bad blob format, sequence missing"); + + // Enumerate the main NegTokenInit sequence + + DERSequence negTokInitSeq = (DERSequence) derTagSeq.getObject(); + Enumeration seqEnum = negTokInitSeq.getObjects(); + + while ( seqEnum.hasMoreElements()) + { + // Read an object from the sequence + + derObj = (DERObject) seqEnum.nextElement(); + if ( derObj instanceof DERTaggedObject) + { + // Tag 0 should be a sequence of object identifiers + + DERTaggedObject derTag = (DERTaggedObject) derObj; + if ( derTag.getTagNo() == 0 && derTag.getObject() instanceof DERSequence) + { + DERSequence derSeq = (DERSequence) derTag.getObject(); + Enumeration typesEnum = derSeq.getObjects(); + + // Allocate the OID list + + m_mechTypes = new Oid[derSeq.size()]; + int idx = 0; + + while( typesEnum.hasMoreElements()) + { + derObj = (DERObject) typesEnum.nextElement(); + if ( derObj instanceof DERObjectIdentifier) + { + derOid = (DERObjectIdentifier) derObj; + try + { + m_mechTypes[idx++] = new Oid( derOid.getId()); + } + catch (GSSException ex) + { + throw new IOException("Bad mechType OID"); + } + } + } + } + else if ( derTag.getTagNo() == 1 && derTag.getObject() instanceof DERBitString) + { + // Context flags + + } + else if ( derTag.getTagNo() == 2 && derTag.getObject() instanceof DEROctetString) + { + // Unpack the mechToken + + DEROctetString derStr = (DEROctetString) derTag.getObject(); + m_mechToken = derStr.getOctets(); + } + else if ( derTag.getTagNo() == 3 &&derTag.getObject() instanceof DEROctetString) + { + // mechListMIC + + } + else if ( derTag.getTagNo() == 3 && derTag.getObject() instanceof DERSequence) + { + // mechListMIC (Microsoft) + + DERSequence derSeq = (DERSequence) derTag.getObject(); + + Enumeration subEnum = derSeq.getObjects(); + while( subEnum.hasMoreElements()) + { + derObj = (DERObject) subEnum.nextElement(); + System.out.println("mechListMIC Seq: " + derObj); + } + } + else + throw new IOException("Bad format, unexpected type"); + } + else + throw new IOException("Bad format, untagged type"); + } + } + + /** + * Encode an SPNEGO NegTokenInit blob + * + * @return byte[] + * @exception IOException + */ + public byte[] encode() throws IOException + { + ByteArrayOutputStream tokStream = new ByteArrayOutputStream(); + + // Create an SPNEGO NegTokenInit token + + DEROutputStream derOut = new DEROutputStream( tokStream); + + derOut.writeObject( new DERObjectIdentifier( OID.ID_SPNEGO)); + ASN1EncodableVector asnList = new ASN1EncodableVector(); + + // Build the mechTypes sequence + + ASN1EncodableVector mechTypesList = new ASN1EncodableVector(); + + for ( Oid mechType : m_mechTypes) + { + mechTypesList.add( new DERObjectIdentifier( mechType.toString())); + } + + asnList.add( new DERTaggedObject( true, 0, new DERSequence( mechTypesList))); + + // Build the mechListMIC + // + // Note: This field is not as specified + + if ( m_mecListMICPrincipal != null) + { + ASN1EncodableVector micList = new ASN1EncodableVector(); + + micList.add( new DERTaggedObject( true, 0, new DERGeneralString( m_mecListMICPrincipal))); + asnList.add( new DERTaggedObject( true, 3, new DERSequence( micList))); + } + + // Generate the SPNEGO NegTokenInit blob + + derOut.writeObject( new DERTaggedObject( true, 0, new DERSequence( asnList))); + DERObject token = new DERUnknownTag( DERTags.CONSTRUCTED | DERTags.APPLICATION, tokStream.toByteArray()); + + tokStream = new ByteArrayOutputStream(); + derOut = new DEROutputStream( tokStream); + derOut.writeObject( token); + + return tokStream.toByteArray(); + } + + /** + * Return the NegTokenInit object as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("[NegTokenInit "); + + if ( m_mechTypes != null) + { + str.append("mechTypes="); + for ( Oid oid : m_mechTypes) + { + str.append(oid.toString()); + str.append(","); + } + } + + if ( m_contextFlags != -1) + { + str.append(" context=0x"); + str.append(Integer.toHexString(m_contextFlags)); + } + + if ( m_mechToken != null) + { + str.append(" token="); + str.append(m_mechToken.length); + str.append(" bytes"); + } + + if ( m_mecListMICPrincipal != null) + { + str.append(" principal="); + str.append(m_mecListMICPrincipal); + } + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/spnego/NegTokenTarg.java b/source/java/org/alfresco/filesys/server/auth/spnego/NegTokenTarg.java new file mode 100644 index 0000000000..520ee80794 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/spnego/NegTokenTarg.java @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth.spnego; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Enumeration; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.DEREnumerated; +import org.bouncycastle.asn1.DERObject; +import org.bouncycastle.asn1.DERObjectIdentifier; +import org.bouncycastle.asn1.DEROctetString; +import org.bouncycastle.asn1.DEROutputStream; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.DERTaggedObject; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.Oid; + +/** + * NegTokenTarg Class + * + *

Contains the details of an SPNEGO NegTokenTarg blob for use with CIFS. + * + * @author gkspencer + */ +public class NegTokenTarg +{ + // Result code + + private int m_result; + + // Supported mechanism + + private Oid m_supportedMech; + + // Response token + + private byte[] m_responseToken; + + /** + * Class constructor for decoding + */ + public NegTokenTarg() + { + } + + /** + * Class constructor + * + * @param result int + * @param mech Oid + * @param response byte[] + */ + public NegTokenTarg(int result, Oid mech, byte[] response) + { + m_result = result; + m_supportedMech = mech; + m_responseToken = response; + } + + /** + * Return the result + * + * @return int + */ + public final int getResult() + { + return m_result; + } + + /** + * Return the supported mech type Oid + * + * @return Oid + */ + public final Oid getSupportedMech() + { + return m_supportedMech; + } + + /** + * Determine if there is a valid response token + * + * @return boolean + */ + public final boolean hasResponseToken() + { + return m_responseToken != null ? true : false; + } + + /** + * Return the response token + * + * @return byte[] + */ + public final byte[] getResponseToken() + { + return m_responseToken; + } + + /** + * Decode an SPNEGO NegTokenTarg blob + * + * @param buf byte[] + * @param off int + * @param len int + * @exception IOException + */ + public void decode(byte[] buf, int off, int len) throws IOException + { + // Create a stream around the security blob + + ByteArrayInputStream bytStream = new ByteArrayInputStream( buf, off, len); + ASN1InputStream asnStream = new ASN1InputStream( bytStream); + + // Read the top level object from the security blob + + DERObject derObj = asnStream.readObject(); + + if ( derObj instanceof DERTaggedObject == false) + throw new IOException("Bad blob format (Tagged)"); + + // Access the sequence + + DERTaggedObject derTag = (DERTaggedObject) derObj; + if ( derTag.getObject() instanceof DERSequence == false) + throw new IOException("Bad blob format (Seq)"); + + DERSequence derSeq = (DERSequence) derTag.getObject(); + Enumeration seqEnum = derSeq.getObjects(); + + while ( seqEnum.hasMoreElements()) + { + // Read an object from the sequence + + derObj = (DERObject) seqEnum.nextElement(); + if ( derObj instanceof DERTaggedObject) + { + // Tag 0 should be a status + + derTag = (DERTaggedObject) derObj; + if ( derTag.getTagNo() == 0 && derTag.getObject() instanceof DEREnumerated) + { + // Result code + + DEREnumerated derEnum = (DEREnumerated) derTag.getObject(); + m_result = derEnum.getValue().intValue(); + } + else if ( derTag.getTagNo() == 1 && derTag.getObject() instanceof DERObjectIdentifier) + { + // Mech type + + DERObjectIdentifier derOid = (DERObjectIdentifier) derTag.getObject(); + try + { + m_supportedMech = new Oid(derOid.getId()); + } + catch (GSSException ex) + { + } + } + else if ( derTag.getTagNo() == 2 && derTag.getObject() instanceof DEROctetString) + { + // Unpack the response token + + DEROctetString derStr = (DEROctetString) derTag.getObject(); + m_responseToken = derStr.getOctets(); + } + else if ( derTag.getTagNo() == 3 &&derTag.getObject() instanceof DEROctetString) + { + // mechListMIC + + } + else + throw new IOException("Bad format, unexpected type"); + } + else + throw new IOException("Bad format, untagged type"); + } + } + + /** + * Encode an SPNEGO NegTokenTarg blob + * + * @return byte[] + * @exception IOException + */ + public byte[] encode() throws IOException + { + ByteArrayOutputStream tokStream = new ByteArrayOutputStream(); + + // Create an SPNEGO NegTokenTarg token + + DEROutputStream derOut = new DEROutputStream( tokStream); + + ASN1EncodableVector asnList = new ASN1EncodableVector(); + + // Pack the result code + + asnList.add( new DERTaggedObject( true, 0, new DEREnumerated(m_result))); + + // Pack the supportedMech field + + if ( m_supportedMech != null) + asnList.add( new DERTaggedObject( true, 1, new DERObjectIdentifier( m_supportedMech.toString()))); + + // Pack the response token + + if ( m_responseToken != null) + asnList.add( new DERTaggedObject( true, 2, new DEROctetString(m_responseToken))); + + // Generate the SPNEGO NegTokenTarg blob + + derOut.writeObject( new DERTaggedObject( true, SPNEGO.NegTokenTarg, new DERSequence( asnList))); + return tokStream.toByteArray(); + } + + /** + * Return the NegtokenTarg object as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("[NegtokenTarg result="); + str.append( SPNEGO.asResultString( getResult())); + + str.append(" oid="); + str.append( getSupportedMech()); + + str.append(" response="); + if ( hasResponseToken()) + { + str.append(getResponseToken().length); + str.append(" bytes"); + } + else + str.append("null"); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/spnego/OID.java b/source/java/org/alfresco/filesys/server/auth/spnego/OID.java new file mode 100644 index 0000000000..b77e66cb3d --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/spnego/OID.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth.spnego; + +import org.ietf.jgss.GSSException; +import org.ietf.jgss.Oid; + +/** + * OID Class + * + *

Contains Oids used by SPNEGO + * + * @author gkspencer + */ +public class OID +{ + // IDs + + public static final String ID_SPNEGO = "1.3.6.1.5.5.2"; + + // Kerberos providers + + public static final String ID_KERBEROS5 = "1.2.840.113554.1.2.2"; + public static final String ID_MSKERBEROS5 = "1.2.840.48018.1.2.2"; + + // Microsoft NTLM security support provider + + public static final String ID_NTLMSSP = "1.3.6.1.4.1.311.2.2.10"; + + // OIDs + + public static Oid SPNEGO; + + public static Oid KERBEROS5; + public static Oid MSKERBEROS5; + + public static Oid NTLMSSP; + + /** + * Static initializer + */ + + static { + + // Create the OIDs + + try + { + SPNEGO = new Oid(ID_SPNEGO); + + KERBEROS5 = new Oid(ID_KERBEROS5); + MSKERBEROS5 = new Oid( ID_MSKERBEROS5); + + NTLMSSP = new Oid(ID_NTLMSSP); + } + catch ( GSSException ex) + { + } + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/spnego/SPNEGO.java b/source/java/org/alfresco/filesys/server/auth/spnego/SPNEGO.java new file mode 100644 index 0000000000..891c59f0b9 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/spnego/SPNEGO.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.filesys.server.auth.spnego; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +import org.bouncycastle.asn1.ASN1InputStream; +import org.bouncycastle.asn1.DERApplicationSpecific; +import org.bouncycastle.asn1.DERObject; +import org.bouncycastle.asn1.DERTaggedObject; + +/** + * SPNEGO Class + * + *

Contains SPNEGO constants + * + * @author gkspencer + */ +public class SPNEGO +{ + // Message types + + public static final int NegTokenInit = 0; + public static final int NegTokenTarg = 1; + + // NegTokenInit context flags + + public static final int ContextDelete = 0; + public static final int ContextMutual = 1; + public static final int ContextReplay = 2; + public static final int ContextSequence = 3; + public static final int ContextAnon = 4; + public static final int ContextConf = 5; + public static final int ContextInteg = 6; + + // NegTokenTarg result codes + + public static final int AcceptCompleted = 0; + public static final int AcceptIncomplete = 1; + public static final int Reject = 2; + + /** + * Return a result code as a string + * + * @param res int + * @return String + */ + public static String asResultString(int res) + { + String resStr = null; + + switch ( res) + { + case AcceptCompleted: + resStr = "AcceptCompleted"; + break; + case AcceptIncomplete: + resStr = "AcceptIncomplete"; + break; + case Reject: + resStr = "Reject"; + break; + default: + resStr = "" + res; + break; + } + + return resStr; + } + + /** + * Determine the SPNEGO token type + * + * @param buf byte[] + * @param off int + * @param len int + * @return int + * @exception IOException + */ + public static int checkTokenType( byte[] buf, int off, int len) + throws IOException + { + // Create a stream around the security blob + + ByteArrayInputStream bytStream = new ByteArrayInputStream( buf, off, len); + ASN1InputStream asnStream = new ASN1InputStream( bytStream); + + // Read the top level object from the security blob + + DERObject derObj = asnStream.readObject(); + int tokType = -1; + + if ( derObj instanceof DERApplicationSpecific) + { + // Looks like a NegTokenInit token + + tokType = NegTokenInit; + } + else if ( derObj instanceof DERTaggedObject) + { + // Check the tag number + + DERTaggedObject derTag = (DERTaggedObject) derObj; + if ( derTag.getTagNo() == 1) + tokType = NegTokenTarg; + } + + // Close the streams + + asnStream.close(); + bytStream.close(); + + // Return the token type + + return tokType; + } +} diff --git a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java index f7f3aaf65e..d24edcd9ea 100644 --- a/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java +++ b/source/java/org/alfresco/filesys/server/config/ServerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005 Alfresco, Inc. + * Copyright (C) 2005-2006 Alfresco, Inc. * * Licensed under the Mozilla Public License version 1.1 * with a permitted attribution clause. You may obtain a @@ -20,7 +20,6 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; -import java.net.SocketException; import java.net.UnknownHostException; import java.security.Provider; import java.security.Security; @@ -46,10 +45,7 @@ import org.alfresco.filesys.netbios.RFCNetBIOSProtocol; import org.alfresco.filesys.netbios.win32.Win32NetBIOS; import org.alfresco.filesys.server.NetworkServer; import org.alfresco.filesys.server.NetworkServerList; -import org.alfresco.filesys.server.auth.LocalAuthenticator; -import org.alfresco.filesys.server.auth.SrvAuthenticator; -import org.alfresco.filesys.server.auth.UserAccount; -import org.alfresco.filesys.server.auth.UserAccountList; +import org.alfresco.filesys.server.auth.CifsAuthenticator; import org.alfresco.filesys.server.auth.acl.ACLParseException; import org.alfresco.filesys.server.auth.acl.AccessControl; import org.alfresco.filesys.server.auth.acl.AccessControlList; @@ -68,8 +64,6 @@ import org.alfresco.filesys.server.filesys.DiskDeviceContext; import org.alfresco.filesys.server.filesys.DiskInterface; import org.alfresco.filesys.server.filesys.DiskSharedDevice; import org.alfresco.filesys.server.filesys.HomeShareMapper; -import org.alfresco.filesys.smb.Dialect; -import org.alfresco.filesys.smb.DialectSelector; import org.alfresco.filesys.smb.ServerType; import org.alfresco.filesys.util.IPAddress; import org.alfresco.filesys.util.X64; @@ -138,15 +132,19 @@ public class ServerConfiguration implements ApplicationListener }; // Token name to substitute current server name into the CIFS server name + private static final String TokenLocalName = "${localname}"; - // Acegi authentication manager + // Authentication manager + private AuthenticationManager authenticationManager; // Configuration service + private ConfigService configService; - /** the device to connect use */ + // Disk interface to use for shared filesystems + private DiskInterface diskInterface; // Runtime platform type @@ -159,46 +157,53 @@ public class ServerConfiguration implements ApplicationListener private boolean m_ftpEnable = true; // Server name + private String m_name; // Server type, used by the host announcer + private int m_srvType = ServerType.WorkStation + ServerType.Server + ServerType.NTServer; // Active server list + private NetworkServerList m_serverList; // Server comment + private String m_comment; // Server domain + private String m_domain; // Network broadcast mask string + private String m_broadcast; // Announce the server to network neighborhood, announcement interval in // minutes - private boolean m_announce; + private boolean m_announce; private int m_announceInterval; - // Default SMB dialects to enable - private DialectSelector m_dialects; - // List of shared devices + private SharedDeviceList m_shareList; // Authenticator, used to authenticate users and share connections. - private SrvAuthenticator m_authenticator; + + private CifsAuthenticator m_authenticator; // Share mapper + private ShareMapper m_shareMapper; // Access control manager + private AccessControlManager m_aclManager; - // Global access control list, applied to all shares that do not have access - // controls + // Global access control list, applied to all shares that do not have access controls + private AccessControlList m_globalACLs; private boolean m_nbDebug = false; @@ -206,31 +211,31 @@ public class ServerConfiguration implements ApplicationListener private boolean m_announceDebug = false; // Default session debugging setting + private int m_sessDebug; // Flags to indicate if NetBIOS, native TCP/IP SMB and/or Win32 NetBIOS // should be enabled + private boolean m_netBIOSEnable = true; - private boolean m_tcpSMBEnable = false; - private boolean m_win32NBEnable = false; // Address to bind the SMB server to, if null all local addresses are used + private InetAddress m_smbBindAddress; - // Address to bind the NetBIOS name server to, if null all addresses are - // used + // Address to bind the NetBIOS name server to, if null all addresses are used + private InetAddress m_nbBindAddress; // WINS servers + private InetAddress m_winsPrimary; private InetAddress m_winsSecondary; - // User account list - private UserAccountList m_userList; - // Enable/disable Macintosh extension SMBs + private boolean m_macExtensions; // -------------------------------------------------------------------------------- @@ -238,13 +243,16 @@ public class ServerConfiguration implements ApplicationListener // // Server name to register under Win32 NetBIOS, if not set the main server // name is used + private String m_win32NBName; // LANA to be used for Win32 NetBIOS, if not specified the first available // is used + private int m_win32NBLANA = -1; // Send out host announcements via the Win32 NetBIOS interface + private boolean m_win32NBAnnounce = false; private int m_win32NBAnnounceInterval; @@ -277,10 +285,12 @@ public class ServerConfiguration implements ApplicationListener // Global server configuration // // Timezone name and offset from UTC in minutes + private String m_timeZone; private int m_tzOffset; // JCE provider class name + private String m_jceProviderClass; // Local server name and domain/workgroup name @@ -288,7 +298,8 @@ public class ServerConfiguration implements ApplicationListener private String m_localName; private String m_localDomain; - /** flag indicating successful initialisation */ + // flag to indicate successful initialization + private boolean initialised; // Main authentication service, public API @@ -314,23 +325,6 @@ public class ServerConfiguration implements ApplicationListener m_shareList = new SharedDeviceList(); - // Allocate the SMB dialect selector, and initialize using the default - // list of dialects - - m_dialects = new DialectSelector(); - - m_dialects.AddDialect(Dialect.DOSLanMan1); - m_dialects.AddDialect(Dialect.DOSLanMan2); - m_dialects.AddDialect(Dialect.LanMan1); - m_dialects.AddDialect(Dialect.LanMan2); - m_dialects.AddDialect(Dialect.LanMan2_1); - m_dialects.AddDialect(Dialect.NT); - - // Use the local authenticator, that allows locally defined users to connect to the - // server - - setAuthenticator(new LocalAuthenticator(), null, true); - // Use the default share mapper m_shareMapper = new DefaultShareMapper(); @@ -1328,7 +1322,7 @@ public class ServerConfiguration implements ApplicationListener // Parse the path - FTPPath ftpPath = new FTPPath(rootPath); + new FTPPath(rootPath); // Set the root path @@ -1419,10 +1413,32 @@ public class ServerConfiguration implements ApplicationListener } } + // Get the top level filesystems confgiruation element + + ConfigElement filesystems = config.getConfigElement("filesystems"); + // Get the filesystem configuration elements - List filesysElems = config.getConfigElementList("filesystem"); + List filesysElems = null; + if ( filesystems != null) + { + // Get the list of filesystems + + filesysElems = filesystems.getChildren(); + } + else + { + // Check for the old style configuration + + filesysElems = config.getConfigElementList( "filesystem"); + + // Warn that the configuration is using the old format + + logger.warn("Old style file-servers.xml configuration being used"); + } + // Process the filesystems list + if (filesysElems != null) { @@ -1494,6 +1510,12 @@ public class ServerConfiguration implements ApplicationListener } } } + else + { + // No filesystems defined + + logger.warn("No filesystems defined"); + } } /** @@ -1544,7 +1566,7 @@ public class ServerConfiguration implements ApplicationListener String authType = authElem.getAttribute("type"); if (authType == null) - throw new AlfrescoRuntimeException("Authenticator type not specified"); + authType = "alfresco"; // Get the authentication component type @@ -1552,10 +1574,9 @@ public class ServerConfiguration implements ApplicationListener // Set the authenticator class to use - SrvAuthenticator auth = null; - if (authType.equalsIgnoreCase("local")) - auth = new LocalAuthenticator(); - else if (authType.equalsIgnoreCase("passthru")) + CifsAuthenticator auth = null; + + if (authType.equalsIgnoreCase("passthru")) { // Check if the appropriate authentication component type is configured @@ -1568,14 +1589,6 @@ public class ServerConfiguration implements ApplicationListener if ( auth == null) throw new AlfrescoRuntimeException("Failed to load passthru authenticator"); } - else if (authType.equalsIgnoreCase("acegi")) - { - // Load the Acegi authenticator dynamically - - auth = loadAuthenticatorClass("org.alfresco.filesys.server.auth.passthru.AcegiPassthruAuthenticator"); - if ( auth == null) - throw new AlfrescoRuntimeException("Failed to load Acegi passthru authenticator"); - } else if (authType.equalsIgnoreCase("alfresco")) { // Standard authenticator requires MD4 or passthru based authentication @@ -1592,6 +1605,15 @@ public class ServerConfiguration implements ApplicationListener if ( auth == null) throw new AlfrescoRuntimeException("Failed to load Alfresco authenticator"); } + else if( authType.equalsIgnoreCase("enterprise")) + { + // Load the Enterprise authenticator dynamically + + auth = loadAuthenticatorClass("org.alfresco.filesys.server.auth.EnterpriseCifsAuthenticator"); + + if ( auth == null) + throw new AlfrescoRuntimeException("Failed to load Enterprise authenticator"); + } else throw new AlfrescoRuntimeException("Invalid authenticator type, " + authType); @@ -1605,31 +1627,6 @@ public class ServerConfiguration implements ApplicationListener setAuthenticator(auth, authElem, allowGuest); auth.setMapToGuest( mapGuest); } - - // Add the users - - ConfigElement usersElem = config.getConfigElement("users"); - if (usersElem != null) - { - - // Get the list of user elements - - List userElemList = usersElem.getChildren(); - - for (int i = 0; i < userElemList.size(); i++) - { - - // Get the current user element - - ConfigElement curUserElem = userElemList.get(i); - - if (curUserElem.getName().equals("localuser")) - { - processUser(curUserElem); - } - } - } - } /** @@ -1723,62 +1720,6 @@ public class ServerConfiguration implements ApplicationListener return acls; } - /** - * Add a user account - * - * @param user ConfigElement - */ - private final void processUser(ConfigElement user) - { - - // Get the username - - String attr = user.getAttribute("name"); - if (attr == null || attr.length() == 0) - throw new AlfrescoRuntimeException("User name not specified, or zero length"); - - // Check if the user already exists - - String userName = attr; - - if (hasUserAccounts() && getUserAccounts().findUser(userName) != null) - throw new AlfrescoRuntimeException("User " + userName + " already defined"); - - // Get the password for the account - - ConfigElement elem = user.getChild("password"); - if (elem == null) - throw new AlfrescoRuntimeException("No password specified for user " + userName); - - String password = elem.getValue(); - - // Create the user account - - UserAccount userAcc = new UserAccount(userName, password); - - // Check if the user in an administrator - - if (user.getChild("administrator") != null) - userAcc.setAdministrator(true); - - // Get the real user name and comment - - elem = user.getChild("realname"); - if (elem != null) - userAcc.setRealName(elem.getValue()); - - elem = user.getChild("comment"); - if (elem != null) - userAcc.setComment(elem.getValue()); - - // Add the user account - - UserAccountList accList = getUserAccounts(); - if (accList == null) - setUserAccounts(new UserAccountList()); - getUserAccounts().addUser(userAcc); - } - /** * Parse the platforms attribute returning the set of platform ids * @@ -1938,11 +1879,11 @@ public class ServerConfiguration implements ApplicationListener /** * Get the authenticator object that is used to provide user and share connection - * authentication. + * authentication for CIFS. * - * @return Authenticator + * @return CifsAuthenticator */ - public final SrvAuthenticator getAuthenticator() + public final CifsAuthenticator getAuthenticator() { return m_authenticator; } @@ -2057,16 +1998,6 @@ public class ServerConfiguration implements ApplicationListener return m_domain; } - /** - * Return the enabled SMB dialects that the server will use when negotiating sessions. - * - * @return DialectSelector - */ - public final DialectSelector getEnabledDialects() - { - return m_dialects; - } - /** * Return the server name. * @@ -2117,16 +2048,6 @@ public class ServerConfiguration implements ApplicationListener return m_shareMapper; } - /** - * Return the user account list. - * - * @return UserAccountList - */ - public final UserAccountList getUserAccounts() - { - return m_userList; - } - /** * Return the Win32 NetBIOS server name, if null the default server name will be used * @@ -2470,18 +2391,6 @@ public class ServerConfiguration implements ApplicationListener return m_macExtensions; } - /** - * Determine if there are any user accounts defined. - * - * @return boolean - */ - public final boolean hasUserAccounts() - { - if (m_userList != null && m_userList.numberOfUsers() > 0) - return true; - return false; - } - /** * Determine if NetBIOS SMB is enabled * @@ -2543,18 +2452,17 @@ public class ServerConfiguration implements ApplicationListener } /** - * Set the authenticator to be used to authenticate users and share connections. + * Set the authenticator to be used to authenticate users and share connections for CIFS. * - * @param auth SrvAuthenticator + * @param auth CifsAuthenticator * @param params ConfigElement * @param allowGuest boolean */ - public final void setAuthenticator(SrvAuthenticator auth, ConfigElement params, boolean allowGuest) + public final void setAuthenticator(CifsAuthenticator auth, ConfigElement params, boolean allowGuest) { // Set the server authenticator mode and guest access - auth.setAccessMode(SrvAuthenticator.USER_MODE); auth.setAllowGuest(allowGuest); // Initialize the authenticator using the parameter values @@ -2733,16 +2641,6 @@ public class ServerConfiguration implements ApplicationListener m_sessDebug = flags; } - /** - * Set the user account list. - * - * @param users UserAccountList - */ - public final void setUserAccounts(UserAccountList users) - { - m_userList = users; - } - /** * Set the global access control list * @@ -3061,14 +2959,14 @@ public class ServerConfiguration implements ApplicationListener } /** - * Load an authenticator using dyanmic loading + * Load a CIFS authenticator using dyanmic loading * * @param className String - * @return SrvAuthenticator + * @return CifsAuthenticator */ - private final SrvAuthenticator loadAuthenticatorClass(String className) + private final CifsAuthenticator loadAuthenticatorClass(String className) { - SrvAuthenticator srvAuth = null; + CifsAuthenticator srvAuth = null; try { @@ -3078,8 +2976,8 @@ public class ServerConfiguration implements ApplicationListener // Verify that the class is an authenticator - if ( authObj instanceof SrvAuthenticator) - srvAuth = (SrvAuthenticator) authObj; + if ( authObj instanceof CifsAuthenticator) + srvAuth = (CifsAuthenticator) authObj; } catch (Exception ex) {