From 7669f098bceb03a545f2a4ebe1bd9bde98c937bb Mon Sep 17 00:00:00 2001 From: Paul Holmes-Higgin Date: Wed, 3 May 2006 20:15:10 +0000 Subject: [PATCH] filesys enterpise files git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2748 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../auth/ntlm/AlfrescoAuthenticator.java | 426 +++++ .../filesys/server/auth/ntlm/NTLM.java | 72 + .../server/auth/ntlm/NTLMLogonDetails.java | 301 +++ .../filesys/server/auth/ntlm/NTLMMessage.java | 535 ++++++ .../server/auth/ntlm/NTLMMessageTest.java | 164 ++ .../filesys/server/auth/ntlm/TargetInfo.java | 121 ++ .../server/auth/ntlm/Type1NTLMMessage.java | 215 +++ .../server/auth/ntlm/Type2NTLMMessage.java | 331 ++++ .../server/auth/ntlm/Type3NTLMMessage.java | 324 ++++ .../passthru/AcegiPassthruAuthenticator.java | 296 +++ .../auth/passthru/AuthSessionFactory.java | 929 ++++++++++ .../auth/passthru/AuthenticateSession.java | 1647 +++++++++++++++++ .../auth/passthru/PassthruAuthenticator.java | 693 +++++++ .../server/auth/passthru/PassthruDetails.java | 76 + .../auth/passthru/PassthruServerDetails.java | 187 ++ .../server/auth/passthru/PassthruServers.java | 727 ++++++++ .../server/auth/passthru/SMBPacket.java | 1378 ++++++++++++++ .../auth/passthru/TcpipSMBNetworkSession.java | 293 +++ .../smb/repo/ContentIOControlHandler.java | 596 ++++++ .../alfresco/filesys/smb/repo/IOControl.java | 76 + 20 files changed, 9387 insertions(+) create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/NTLM.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/NTLMLogonDetails.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessage.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessageTest.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/TargetInfo.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/Type1NTLMMessage.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/Type2NTLMMessage.java create mode 100644 source/java/org/alfresco/filesys/server/auth/ntlm/Type3NTLMMessage.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/AcegiPassthruAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/AuthSessionFactory.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/PassthruDetails.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/PassthruServerDetails.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/SMBPacket.java create mode 100644 source/java/org/alfresco/filesys/server/auth/passthru/TcpipSMBNetworkSession.java create mode 100644 source/java/org/alfresco/filesys/smb/repo/ContentIOControlHandler.java create mode 100644 source/java/org/alfresco/filesys/smb/repo/IOControl.java diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java new file mode 100644 index 0000000000..e9fd96dbde --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/AlfrescoAuthenticator.java @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +import java.security.NoSuchAlgorithmException; +import net.sf.acegisecurity.Authentication; + +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.auth.ClientInfo; +import org.alfresco.filesys.server.auth.SrvAuthenticator; +import org.alfresco.filesys.smb.server.SMBSrvSession; +import org.alfresco.filesys.util.DataPacker; +import org.alfresco.repo.security.authentication.NTLMMode; +import org.alfresco.repo.security.authentication.ntlm.NTLMPassthruToken; + +/** + * Alfresco Authenticator Class + * + *

The Alfresco authenticator implementation enables user level security mode using the Alfresco authentication + * component. + * + *

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. + * + * @author GKSpencer + */ +public class AlfrescoAuthenticator extends SrvAuthenticator +{ + /** + * Default Constructor + * + *

Default to user mode security with encrypted password support. + */ + public AlfrescoAuthenticator() + { + setAccessMode(SrvAuthenticator.USER_MODE); + setEncryptedPasswords(true); + } + + /** + * Validate that the authentication component supports the required mode + * + * @return boolean + */ + protected boolean validateAuthenticationMode() + { + // Make sure the authentication component supports MD4 hashed passwords or passthru mode + + if ( m_authComponent.getNTLMMode() != NTLMMode.MD4_PROVIDER && + m_authComponent.getNTLMMode() != NTLMMode.PASS_THROUGH) + return false; + return true; + } + + /** + * 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 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) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Null CIFS logon allowed"); + + return SrvAuthenticator.AUTH_ALLOW; + } + + // Check if the client is already authenticated, and it is not a null logon + + if ( client.getAuthenticationToken() != null && client.getLogonType() != ClientInfo.LogonNull) + { + // Use the existing authentication token + + m_authComponent.setCurrentUser(client.getUserName()); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Re-using existing authentication token"); + + // Return the authentication status + + return client.getLogonType() != ClientInfo.LogonGuest ? AUTH_ALLOW : AUTH_GUEST; + } + + // Check if this is a guest logon + + int authSts = AUTH_DISALLOW; + + if ( client.isGuest() || client.getUserName().equalsIgnoreCase(getGuestUserName())) + { + // Check if guest logons are allowed + + if ( allowGuest() == false) + return AUTH_DISALLOW; + + // Get a guest authentication token + + doGuestLogon( client, sess); + + // Indicate logged on as guest + + authSts = AUTH_GUEST; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Authenticated user " + client.getUserName() + " sts=" + getStatusAsString(authSts)); + + // Return the guest status + + return authSts; + } + + // Check if MD4 or passthru mode is configured + + else if ( m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER) + { + // Perform local MD4 password check + + authSts = doMD4UserAuthentication(client, sess, alg); + } + else + { + // Perform passthru authentication password check + + authSts = doPassthruUserAuthentication(client, sess, alg); + } + + // Check if the logon status indicates a guest logon + + if ( authSts == AUTH_GUEST) + { + // Only allow the guest logon if user mapping is enabled + + if ( mapUnknownUserToGuest()) + { + // Logon as guest, setup the security context + + doGuestLogon( client, sess); + } + else + { + // Do not allow the guest logon + + authSts = AUTH_DISALLOW; + } + } + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Authenticated user " + client.getUserName() + " sts=" + getStatusAsString(authSts) + + " via " + (m_authComponent.getNTLMMode() == NTLMMode.MD4_PROVIDER ? "MD4" : "Passthru")); + + // Return the authentication status + + 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); + } + else + { + // Create an authentication token for the session + + NTLMPassthruToken authToken = new NTLMPassthruToken(); + + // Run the first stage of the passthru authentication to get the challenge + + m_authComponent.authenticate( authToken); + + // Save the authentication token for the second stage of the authentication + + sess.setAuthenticationToken(authToken); + + // Get the challenge from the token + + if ( authToken.getChallenge() != null) + key = authToken.getChallenge().getBytes(); + } + + // Return the challenge + + return key; + } + + /** + * Perform MD4 user authentication + * + * @param client Client information + * @param sess Server session + * @param alg Encryption algorithm + * @return int + */ + private final int doMD4UserAuthentication(ClientInfo client, SrvSession sess, int alg) + { + // 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) + { + // 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; + + try + { + // 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 = getEncryptor().doNTLM1Encryption(p21, sess.getChallengeKey()); + + // Validate the password + + byte[] clientHash = client.getPassword(); + + if ( clientHash == null || clientHash.length != localHash.length) + return SrvAuthenticator.AUTH_BADPASSWORD; + + for ( int i = 0; i < clientHash.length; i++) + { + if ( clientHash[i] != localHash[i]) + return SrvAuthenticator.AUTH_BADPASSWORD; + } + + // Set the current user to be authenticated, save the authentication token + + client.setAuthenticationToken( m_authComponent.setCurrentUser(client.getUserName())); + + // Get the users home folder node, if available + + getHomeFolderForUser( client); + + // Passwords match, grant access + + return SrvAuthenticator.AUTH_ALLOW; + } + catch (NoSuchAlgorithmException ex) + { + } + + // Error during password check, do not allow access + + return SrvAuthenticator.AUTH_DISALLOW; + } + + // 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; + + // User does not exist, check if guest access is allowed + + return allowGuest() ? SrvAuthenticator.AUTH_GUEST : SrvAuthenticator.AUTH_DISALLOW; + } + + /** + * Perform passthru user authentication + * + * @param client Client information + * @param sess Server session + * @param alg Encryption algorithm + * @return int + */ + private final int doPassthruUserAuthentication(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 + { + // 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 + + Authentication genAuthToken = null; + + try + { + // Run the second stage of the passthru authentication + + genAuthToken = m_authComponent.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; + } + } + else + { + + // Allow the user full access to the server + + authSts = SrvAuthenticator.AUTH_ALLOW; + } + + // Set the current user to be authenticated, save the authentication token + + client.setAuthenticationToken( genAuthToken); + + // Get the users home folder node, if available + + getHomeFolderForUser( client); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Auth token " + genAuthToken); + } + catch ( Exception ex) + { + logger.error("Error during passthru authentication", ex); + } + + // Clear the authentication token + + sess.setAuthenticationToken(null); + + // Return the authentication status + + return authSts; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLM.java b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLM.java new file mode 100644 index 0000000000..af4edf60ea --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLM.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +/** + * NTLM Constants Class + * + * @author GKSpencer + */ +public class NTLM +{ + // Signature + + public static final byte[] Signature = "NTLMSSP\u0000".getBytes(); + + // Message types + + public static final int Type1 = 1; + public static final int Type2 = 2; + public static final int Type3 = 3; + + // NTLM flags + + public static final int FlagNegotiateUnicode = 0x00000001; + public static final int FlagNegotiateOEM = 0x00000002; + public static final int FlagRequestTarget = 0x00000004; + public static final int FlagNegotiateSign = 0x00000010; + public static final int FlagNegotiateSeal = 0x00000020; + public static final int FlagDatagramStyle = 0x00000040; + public static final int FlagLanManKey = 0x00000080; + 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 FlagNTLM2Key = 0x00080000; + public static final int FlagTargetInfo = 0x00800000; + public static final int Flag128Bit = 0x20000000; + public static final int FlagKeyExchange = 0x40000000; + public static final int Flag56Bit = 0x80000000; + + // Target information types + + public static final int TargetServer = 0x0001; + public static final int TargetDomain = 0x0002; + public static final int TargetFullDNS = 0x0003; + public static final int TargetDNSDomain = 0x0004; +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMLogonDetails.java b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMLogonDetails.java new file mode 100644 index 0000000000..21ab4d6e17 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMLogonDetails.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +import java.util.Date; + +import net.sf.acegisecurity.Authentication; + +/** + * NTLM Logon Details Class + * + *

Contains the details from the NTLM authentication session that was used to authenticate a user. + * + * @author GKSpencer + */ +public class NTLMLogonDetails +{ + // User name, workstation name and domain + + private String m_user; + private String m_workstation; + private String m_domain; + + // Authentication server name/address + + private String m_authSrvAddr; + + // Date/time the user was authenticated + + private long m_authTime; + + // User logged on via guest access + + private boolean m_guestAccess; + + // Cached type 2 NTLM message and copy of the NTLM hash used to successfully logon + + private Type2NTLMMessage m_type2Msg; + private byte[] m_ntlmHash; + + // Authentication token, used for passthru mode + + private Authentication m_authToken; + + /** + * Default constructor + */ + public NTLMLogonDetails() + { + } + + /** + * Class constructor + * + * @param user String + * @param wks String + * @param domain String + * @param guest boolean + * @param authSrv String + */ + public NTLMLogonDetails(String user, String wks, String domain, boolean guest, String authSrv) + { + setDetails(user, wks, domain, guest, authSrv); + } + + /** + * Return the user name + * + * @return String + */ + public final String getUserName() + { + return m_user; + } + + /** + * Return the workstation name + * + * @return String + */ + public final String getWorkstation() + { + return m_workstation; + } + + /** + * Return the domain name + * + * @return String + */ + public final String getDomain() + { + return m_domain; + } + + /** + * Return the authentication server name/address + * + * @return String + */ + public final String getAuthenticationServer() + { + return m_authSrvAddr; + } + + /** + * Return the date/time the user was authenticated + * + * @return long + */ + public final long authenticatedAt() + { + return m_authTime; + } + + /** + * Determine if the type 2 NTLM message has been cached + * + * @return boolean + */ + public final boolean hasType2Message() + { + return m_type2Msg != null ? true : false; + } + + /** + * Return the cached type 2 NTLM message + * + * @return Type2NTLMMessage + */ + public final Type2NTLMMessage getType2Message() + { + return m_type2Msg; + } + + /** + * Determine if there is a cached NTLM hashed password + * + * @return boolean + */ + public final boolean hasNTLMHashedPassword() + { + return m_ntlmHash != null ? true : false; + } + + /** + * Return the cached NTLM hashed password + * + * @return byte[] + */ + public final byte[] getNTLMHashedPassword() + { + return m_ntlmHash; + } + + /** + * Return the challenge key from the type2 message + * + * @return byte[] + */ + public final byte[] getChallengeKey() + { + if ( m_type2Msg != null) + return m_type2Msg.getChallenge(); + return null; + } + + /** + * Determine if the passthru authentication token is valid + * + * @return boolean + */ + public final boolean hasAuthenticationToken() + { + return m_authToken != null ? true : false; + } + + /** + * Return the authentication token + * + * @return Authentication + */ + public final Authentication getAuthenticationToken() + { + return m_authToken; + } + + /** + * Set the authentication date/time + * + * @param authTime long + */ + public final void setAuthenticatedAt(long authTime) + { + m_authTime = authTime; + } + + /** + * Set the client details + * + * @param user String + * @param wks String + * @param domain String + * @param guest boolean + * @param authSrv String + */ + public final void setDetails(String user, String wks, String domain, boolean guest, String authSrv) + { + m_user = user; + m_workstation = wks; + m_domain = domain; + + m_authSrvAddr = authSrv; + + m_guestAccess = guest; + + m_authTime = System.currentTimeMillis(); + } + + /** + * Set the type 2 NTLM message + * + * @param type2 Type2NTLMMessage + */ + public final void setType2Message(Type2NTLMMessage type2) + { + m_type2Msg = type2; + } + + /** + * Set the cached NTLM hashed password + * + * @param ntlmHash byte[] + */ + public final void setNTLMHashedPassword(byte[] ntlmHash) + { + m_ntlmHash = ntlmHash; + } + + /** + * Set the passthru authentication token + * + * @param token Authentication + */ + public final void setAuthenticationToken(Authentication token) + { + m_authToken = token; + } + + /** + * Return the NTLM logon details as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append(getUserName()); + str.append(",Wks:"); + str.append(getWorkstation()); + str.append(",Dom:"); + str.append(getDomain()); + str.append(",AuthSrv:"); + str.append(getAuthenticationServer()); + str.append(","); + str.append(new Date(authenticatedAt())); + + if ( hasAuthenticationToken()) + { + str.append(",token="); + str.append(getAuthenticationToken()); + } + + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessage.java b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessage.java new file mode 100644 index 0000000000..ecd398632d --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessage.java @@ -0,0 +1,535 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +import java.io.UnsupportedEncodingException; + +import org.alfresco.filesys.util.DataPacker; + +/** + * NTLM Message Types Base Class + * + * @author GKSpencer + */ +public abstract class NTLMMessage +{ + // Default buffer size to allocate + + private static final int DefaultBlobSize = 256; + + // Field offsets + + public static final int OffsetSignature = 0; + public static final int OffsetType = 8; + + // Buffer header length + + public static final int BufferHeaderLen = 8; + + // Buffer, offset and lenght of the NTLM blob + + private byte[] m_buf; + private int m_offset; + + private int m_len; + + /** + * Default constructor + */ + protected NTLMMessage() + { + // Allocate a buffer + + m_buf = new byte[DefaultBlobSize]; + m_len = DefaultBlobSize; + } + + /** + * Class constructor + * + * @param buf byte[] + * @param offset int + * @param len int + */ + protected NTLMMessage(byte[] buf, int offset, int len) + { + m_buf = buf; + m_offset = offset; + + m_len = len; + } + + /** + * Return the message type + * + * @return int + */ + public final int isMessageType() + { + return DataPacker.getIntelInt(m_buf, m_offset + OffsetType); + } + + /** + * Return the message flags + * + * @return int + */ + public abstract int getFlags(); + + /** + * Return the state of the specified flag + * + * @param flag int + * @return boolean + */ + public final boolean hasFlag(int flag) + { + return (getFlags() & flag) != 0 ? true : false; + } + /** + * Return the message length + * + * @return int + */ + public int getLength() + { + return m_len; + } + + /** + * Set the message type + * + * @param typ int + */ + public final void setMessageType(int typ) + { + DataPacker.putIntelInt(typ, m_buf, m_offset + OffsetType); + } + + /** + * Copy the NTLM blob data from the specified buffer + * + * @param buf byte[] + * @param offset int + * @param len int + */ + public final void copyFrom(byte[] buf, int offset, int len) + { + // Allocate a new buffer, if required + + if ( m_buf == null || m_offset != 0 || m_buf.length < len) + m_buf = new byte[len]; + + // Copy the security blob data + + System.arraycopy(buf, offset, m_buf, 0, len); + } + + /** + * Return the NTLM message as a byte array + * + * @return byte[] + */ + public final byte[] getBytes() + { + byte[] byts = new byte[getLength()]; + System.arraycopy(m_buf, m_offset, byts, 0, getLength()); + return byts; + } + + /** + * Set the message flags + * + * @param flags int + */ + protected abstract void setFlags(int flags); + + /** + * Initialize the blob + * + * @param typ int + * @param flags int + */ + protected void initializeHeader(int typ, int flags) + { + // Set the signature + + System.arraycopy( NTLM.Signature, 0, m_buf, m_offset, NTLM.Signature.length); + + setMessageType(typ); + setFlags(flags); + } + + /** + * Return a short/16bit value + * + * @param offset int + * @return int + */ + protected final int getShortValue(int offset) + { + return DataPacker.getIntelShort(m_buf, m_offset + offset); + } + + /** + * Return an int/32bit value + * + * @param offset int + * @return int + */ + protected final int getIntValue(int offset) + { + return DataPacker.getIntelInt(m_buf, m_offset + offset); + } + + /** + * Return the offset for a byte value + * + * @param offset int + * @return int + */ + protected final int getByteOffset(int offset) + { + return getIntValue(m_offset + offset + 4); + } + + /** + * Return a byte value that has a header + * + * @param offset int + * @return byte[] + */ + protected final byte[] getByteValue(int offset) + { + // Get the byte block length + + int bLen = getShortValue(m_offset + offset); + if ( bLen == 0) + return null; + + int bOff = getIntValue(m_offset + offset + 4); + return getRawBytes(bOff, bLen); + } + + /** + * Return a block of byte data + * + * @param offset int + * @param len int + * @return byte[] + */ + protected final byte[] getRawBytes(int offset, int len) + { + byte[] byts = new byte[len]; + System.arraycopy(m_buf, m_offset + offset, byts, 0, len); + + return byts; + } + + /** + * Return the length of a string + * + * @param offset int + * @return int + */ + protected final int getStringLength(int offset) + { + int bufpos = m_offset + offset; + + if ( bufpos + 2 > m_len) + return -1; + return DataPacker.getIntelShort(m_buf, bufpos); + } + + /** + * Return the allocated length of a string + * + * @param offset int + * @return int + */ + protected final int getStringAllocatedLength(int offset) + { + int bufpos = m_offset + offset; + + if ( bufpos + 8 > m_len) + return -1; + return DataPacker.getIntelShort(m_buf, bufpos + 2); + } + + /** + * Return the string data offset + * + * @param offset int + * @return int + */ + protected final int getStringOffset(int offset) + { + int bufpos = m_offset + offset; + + if ( bufpos + 8 > m_len) + return -1; + return DataPacker.getIntelInt(m_buf, bufpos + 4); + } + + /** + * Return a string value + * + * @param offset int + * @param isuni boolean + * @return String + */ + protected final String getStringValue(int offset, boolean isuni) + { + int bufpos = m_offset + offset; + + if ( offset + 8 > m_len) + return null; + + // Get the offset to the string + + int len = DataPacker.getIntelShort(m_buf, bufpos); + int pos = DataPacker.getIntelInt(m_buf, bufpos + 4); + + // Get the string value + + if ( pos + len > m_len) + return null; +// if ( isuni) +// len = len / 2; + + // Unpack the string + + String str = null; + try + { + str = new String(m_buf, m_offset + pos, len, isuni ? "UnicodeLittle" : "US-ASCII"); + } + catch (UnsupportedEncodingException ex) + { + ex.printStackTrace(); + } + + return str; + } + + /** + * Return a raw string + * + * @param offset int + * @param len int + * @param isuni boolean + * @return String + */ + protected final String getRawString(int offset, int len, boolean isuni) + { + return DataPacker.getString(m_buf, m_offset + offset, len, isuni); + } + + /** + * Set a short/16bit value + * + * @param offset int + * @param sval int + */ + protected final void setShortValue(int offset, int sval) + { + DataPacker.putIntelShort(sval, m_buf, m_offset + offset); + } + + /** + * Set an int/32bit value + * + * @param offset int + * @param val int + */ + protected final void setIntValue(int offset, int val) + { + DataPacker.putIntelInt(val, m_buf, m_offset + offset); + } + + /** + * Set a raw byte value + * + * @param offset int + * @param byts byte[] + */ + protected final void setRawBytes(int offset, byte[] byts) + { + System.arraycopy(byts, 0, m_buf, m_offset + offset, byts.length); + } + + /** + * Set raw int values + * + * @param offset int + * @param ints int[] + */ + protected final void setRawInts(int offset, int[] ints) + { + int bufpos = m_offset + offset; + + for ( int i = 0; i < ints.length; i++) + { + DataPacker.putIntelInt( ints[i], m_buf, bufpos); + bufpos += 4; + } + } + + /** + * Pack a raw string + * + * @param offset int + * @param str String + * @param isuni boolean + * @return int + */ + protected final int setRawString(int offset, String str, boolean isuni) + { + return DataPacker.putString(str, m_buf, m_offset + offset, false, isuni); + } + + /** + * Zero out an area of bytes + * + * @param offset int + * @param len int + */ + protected final void zeroBytes(int offset, int len) + { + int bufpos = m_offset + offset; + for ( int i = 0; i < len; i++) + m_buf[bufpos++] = (byte) 0; + } + + /** + * Set a byte array value + * + * @param offset int + * @param byts byte[] + * @param dataOffset int + * @return int + */ + protected final int setByteValue(int offset, byte[] byts, int dataOffset) + { + int bytsLen = byts != null ? byts.length : 0; + + if ( m_offset + offset + 12 > m_buf.length || + m_offset + dataOffset + bytsLen > m_buf.length) + throw new ArrayIndexOutOfBoundsException(); + + // Pack the byte pointer block + + DataPacker.putIntelShort(bytsLen, m_buf, m_offset + offset); + DataPacker.putIntelShort(bytsLen, m_buf, m_offset + offset + 2); + DataPacker.putIntelInt(dataOffset, m_buf, m_offset + offset + 4); + + // Pack the bytes + + if ( bytsLen > 0) + System.arraycopy(byts, 0, m_buf, m_offset + dataOffset, bytsLen); + + // Return the new data buffer offset + + return dataOffset + DataPacker.wordAlign(bytsLen); + } + + /** + * Set a string value + * + * @param offset int + * @param val String + * @param strOffset int + * @param isuni boolean + * @return int + */ + protected final int setStringValue(int offset, String val, int strOffset, boolean isuni) + { + // Get the length in bytes + + int len = val.length(); + if ( isuni) + len *= 2; + + if ( m_offset + offset + 8 > m_buf.length || + m_offset + strOffset + len > m_buf.length) + throw new ArrayIndexOutOfBoundsException(); + + // Pack the string pointer block + + + DataPacker.putIntelShort(len, m_buf, m_offset + offset); + DataPacker.putIntelShort(len, m_buf, m_offset + offset + 2); + DataPacker.putIntelInt(strOffset, m_buf, m_offset + offset + 4); + + // Pack the string + + return DataPacker.putString(val, m_buf, m_offset + strOffset, false, isuni) - m_offset; + } + + /** + * Set the message length + * + * @param len int + */ + protected final void setLength(int len) + { + m_len = len; + } + + /** + * Validate and determine the NTLM message type + * + * @param buf byte[] + * @return int + */ + public final static int isNTLMType(byte[] buf) + { + return isNTLMType(buf, 0); + } + + /** + * Validate and determine the NTLM message type + * + * @param buf byte[] + * @param offset int + * @return int + */ + public final static int isNTLMType(byte[] buf, int offset) + { + // Validate the buffer + + if ( buf == null || buf.length < BufferHeaderLen) + return -1; + + for ( int i = 0; i < NTLM.Signature.length; i++) + { + if ( buf[offset + i] != NTLM.Signature[i]) + return -1; + } + + // Get the NTLM message type + + return DataPacker.getIntelInt(buf, offset + OffsetType); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessageTest.java b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessageTest.java new file mode 100644 index 0000000000..2fcc183acc --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/NTLMMessageTest.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +import junit.framework.TestCase; + +/** + * NTLM Message Test Class + * + * @author GKSpencer + */ +public class NTLMMessageTest extends TestCase +{ + /** + * Test type 1 message packing/unpacking + */ + public void testType1Message() + { + // Create a minimal type 1 message + + Type1NTLMMessage ntlmMsg = new Type1NTLMMessage(); + ntlmMsg.buildType1(0, null, null); + + assertEquals("Minimal type 1 message length wrong", 16, ntlmMsg.getLength()); + assertFalse("Minimal type 1 message domain supplied flag set", ntlmMsg.hasFlag(NTLM.FlagDomainSupplied)); + assertFalse("Minimal type 1 message workstation supplied flag set", ntlmMsg.hasFlag(NTLM.FlagWorkstationSupplied)); + assertFalse("Minimal type 1 has domain", ntlmMsg.hasDomain()); + assertNull("Minimal type 1 domain not null", ntlmMsg.getDomain()); + assertFalse("Minimal type 1 has workstation", ntlmMsg.hasWorkstation()); + assertNull("Minimal type 1 workstation not null", ntlmMsg.getWorkstation()); + + // Use a buffer to build a type 1 message + + byte[] buf = new byte[256]; + ntlmMsg = new Type1NTLMMessage(buf, 128, 128); + ntlmMsg.buildType1(0, null, null); + + assertEquals("Minimal type 1 message length wrong", 16, ntlmMsg.getLength()); + assertFalse("Minimal type 1 message domain supplied flag set", ntlmMsg.hasFlag(NTLM.FlagDomainSupplied)); + assertFalse("Minimal type 1 message workstation supplied flag set", ntlmMsg.hasFlag(NTLM.FlagWorkstationSupplied)); + assertFalse("Minimal type 1 has domain", ntlmMsg.hasDomain()); + assertNull("Minimal type 1 domain not null", ntlmMsg.getDomain()); + assertFalse("Minimal type 1 has workstation", ntlmMsg.hasWorkstation()); + assertNull("Minimal type 1 workstation not null", ntlmMsg.getWorkstation()); + + // Test type 1 with domain name only + + String testDomain = "TESTDOMAIN"; + String testWks = "TESTPC"; + + ntlmMsg = new Type1NTLMMessage(); + ntlmMsg.buildType1(0, testDomain, null); + + assertTrue("Minimal type 1 message length wrong", ntlmMsg.getLength() > 16); + assertTrue("Minimal type 1 message domain supplied flag not set", ntlmMsg.hasFlag(NTLM.FlagDomainSupplied)); + assertFalse("Minimal type 1 message workstation supplied flag set", ntlmMsg.hasFlag(NTLM.FlagWorkstationSupplied)); + assertTrue("Minimal type 1 no domain", ntlmMsg.hasDomain()); + assertEquals("Minimal type 1 domain not correct", testDomain, ntlmMsg.getDomain()); + assertFalse("Minimal type 1 has workstation", ntlmMsg.hasWorkstation()); + assertNull("Minimal type 1 workstation not null", ntlmMsg.getWorkstation()); + + // Test type 1 with domain name only with buffer + + ntlmMsg = new Type1NTLMMessage(buf, 128, 128); + ntlmMsg.buildType1(0, testDomain, null); + + assertTrue("Minimal type 1 message length wrong", ntlmMsg.getLength() > 16); + assertTrue("Minimal type 1 message domain supplied flag not set", ntlmMsg.hasFlag(NTLM.FlagDomainSupplied)); + assertFalse("Minimal type 1 message workstation supplied flag set", ntlmMsg.hasFlag(NTLM.FlagWorkstationSupplied)); + assertTrue("Minimal type 1 no domain", ntlmMsg.hasDomain()); + assertEquals("Minimal type 1 domain not correct", testDomain, ntlmMsg.getDomain()); + assertFalse("Minimal type 1 has workstation", ntlmMsg.hasWorkstation()); + assertNull("Minimal type 1 workstation not null", ntlmMsg.getWorkstation()); + + // Test type 1 with workstation name only + + ntlmMsg = new Type1NTLMMessage(); + ntlmMsg.buildType1(0, null, testWks); + + assertTrue("Minimal type 1 message length wrong", ntlmMsg.getLength() > 16); + assertFalse("Minimal type 1 message domain supplied flag set", ntlmMsg.hasFlag(NTLM.FlagDomainSupplied)); + assertTrue("Minimal type 1 message workstation supplied flag not set", ntlmMsg.hasFlag(NTLM.FlagWorkstationSupplied)); + assertFalse("Minimal type 1 has domain", ntlmMsg.hasDomain()); + assertNull("Minimal type 1 domain not null", ntlmMsg.getDomain()); + assertTrue("Minimal type 1 no workstation", ntlmMsg.hasWorkstation()); + assertEquals("Minimal type 1 workstation not correct", testWks, ntlmMsg.getWorkstation()); + + // Test type 1 with domain name only with buffer + + ntlmMsg = new Type1NTLMMessage(buf, 128, 128); + ntlmMsg.buildType1(0, null, testWks); + + assertTrue("Minimal type 1 message length wrong", ntlmMsg.getLength() > 16); + assertFalse("Minimal type 1 message domain supplied flag set", ntlmMsg.hasFlag(NTLM.FlagDomainSupplied)); + assertTrue("Minimal type 1 message workstation supplied flag not set", ntlmMsg.hasFlag(NTLM.FlagWorkstationSupplied)); + assertFalse("Minimal type 1 has domain", ntlmMsg.hasDomain()); + assertNull("Minimal type 1 domain not null", ntlmMsg.getDomain()); + assertTrue("Minimal type 1 no workstation", ntlmMsg.hasWorkstation()); + assertEquals("Minimal type 1 workstation not correct", testWks, ntlmMsg.getWorkstation()); + + // Test type 1 with domain and workstation names + + ntlmMsg = new Type1NTLMMessage(); + ntlmMsg.buildType1(0, testDomain, testWks); + + assertTrue("Minimal type 1 message length wrong", ntlmMsg.getLength() > 16); + assertTrue("Minimal type 1 message domain supplied flag not set", ntlmMsg.hasFlag(NTLM.FlagDomainSupplied)); + assertTrue("Minimal type 1 message workstation supplied flag not set", ntlmMsg.hasFlag(NTLM.FlagWorkstationSupplied)); + assertTrue("Minimal type 1 has domain", ntlmMsg.hasDomain()); + assertEquals("Minimal type 1 domain not correct", testDomain, ntlmMsg.getDomain()); + assertTrue("Minimal type 1 no workstation", ntlmMsg.hasWorkstation()); + assertEquals("Minimal type 1 workstation not correct", testWks, ntlmMsg.getWorkstation()); + + // Test type 1 with domain and workstation names, with buffer + + ntlmMsg = new Type1NTLMMessage(buf, 128, 128); + ntlmMsg.buildType1(0, testDomain, testWks); + + assertTrue("Minimal type 1 message length wrong", ntlmMsg.getLength() > 16); + assertTrue("Minimal type 1 message domain supplied flag not set", ntlmMsg.hasFlag(NTLM.FlagDomainSupplied)); + assertTrue("Minimal type 1 message workstation supplied flag not set", ntlmMsg.hasFlag(NTLM.FlagWorkstationSupplied)); + assertTrue("Minimal type 1 has domain", ntlmMsg.hasDomain()); + assertEquals("Minimal type 1 domain not correct", testDomain, ntlmMsg.getDomain()); + assertTrue("Minimal type 1 no workstation", ntlmMsg.hasWorkstation()); + assertEquals("Minimal type 1 workstation not correct", testWks, ntlmMsg.getWorkstation()); + } + + /** + * Test type 2 message packing/unpacking + */ + public void testType2Message() + { + // No tests yet, only receive type 2 from the server + } + + /** + * Test type 3 message packing/unpacking + */ + public void testType3Message() + { + + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/TargetInfo.java b/source/java/org/alfresco/filesys/server/auth/ntlm/TargetInfo.java new file mode 100644 index 0000000000..91ff3ff061 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/TargetInfo.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +/** + * Target Information Class + * + *

Contains the target information from an NTLM message. + * + * @author GKSpencer + */ +public class TargetInfo +{ + // Target type and name + + private int m_type; + private String m_name; + + /** + * Class constructor + * + * @param type int + * @param name String + */ + public TargetInfo(int type, String name) + { + m_type = type; + m_name = name; + } + + /** + * Return the target type + * + * @return int + */ + public final int isType() + { + return m_type; + } + + /** + * Return the target name + * + * @return String + */ + public final String getName() + { + return m_name; + } + + /** + * Return the target information as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + str.append(getTypeAsString(isType())); + str.append(":"); + str.append(getName()); + str.append("]"); + + return str.toString(); + } + + /** + * Return the target type as a string + * + * @param typ int + * @return String + */ + public final static String getTypeAsString(int typ) + { + String typStr = null; + + switch ( typ) + { + case NTLM.TargetServer: + typStr = "Server"; + break; + case NTLM.TargetDomain: + typStr = "Domain"; + break; + case NTLM.TargetFullDNS: + typStr = "DNS"; + break; + case NTLM.TargetDNSDomain: + typStr = "DNS Domain"; + break; + default: + typStr = "Unknown 0x" + Integer.toHexString(typ); + break; + } + + return typStr; + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/Type1NTLMMessage.java b/source/java/org/alfresco/filesys/server/auth/ntlm/Type1NTLMMessage.java new file mode 100644 index 0000000000..5008dd09ca --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/Type1NTLMMessage.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +/** + * Type1 NTLM Message Class + * + * @author GKSpencer + */ +public class Type1NTLMMessage extends NTLMMessage +{ + // Minimal type 1 message length + + public static final int MinimalMessageLength = 16; + + // Type 1 field offsets + + public static final int OffsetFlags = 12; + public static final int OffsetData = 16; + + /** + * Default constructor + */ + public Type1NTLMMessage() + { + super(); + } + + /** + * Class constructor + * + * @param buf byte[] + */ + public Type1NTLMMessage(byte[] buf) + { + super(buf, 0, buf.length); + } + + /** + * Class constructor + * + * @param buf byte[] + * @param offset int + * @param len int + */ + public Type1NTLMMessage(byte[] buf, int offset, int len) + { + super(buf, offset, len); + } + + /** + * Return the flags value + * + * @return int + */ + public int getFlags() + { + return getIntValue(OffsetFlags); + } + + /** + * Check if the domain security buffer is included + * + * @return boolean + */ + public final boolean hasDomain() + { + if ( getLength() == MinimalMessageLength || hasFlag(NTLM.FlagDomainSupplied) == false) + return false; + return true; + } + + /** + * Return the domain name + * + * @return String + */ + public final String getDomain() + { + if ( hasFlag(NTLM.FlagDomainSupplied) == false) + return null; + + return getStringValue(OffsetData, false); + } + + /** + * Check if the workstation security buffer is included + * + * @return boolean + */ + public final boolean hasWorkstation() + { + if ( getLength() == MinimalMessageLength || hasFlag(NTLM.FlagWorkstationSupplied) == false) + return false; + return true; + } + + /** + * Return the workstation name + * + * @return String + */ + public final String getWorkstation() + { + if ( hasFlag(NTLM.FlagWorkstationSupplied) == false) + return null; + + int bufPos = OffsetData; + if ( hasFlag(NTLM.FlagDomainSupplied)) + bufPos += BufferHeaderLen; + + return getStringValue(bufPos, false); + } + + /** + * Build a type 1 message + * + * @param flags int + * @param domain String + * @param workstation String + */ + public final void buildType1(int flags, String domain, String workstation) + { + int bufPos = OffsetData; + int strOff = OffsetData; + + if ( domain != null) + strOff += BufferHeaderLen; + if ( workstation != null) + strOff += BufferHeaderLen; + + // Pack the domain name + + if ( domain != null) + { + strOff = setStringValue(bufPos, domain, strOff, false); + flags |= NTLM.FlagDomainSupplied; + bufPos += BufferHeaderLen; + } + + // Pack the workstation name + + if ( workstation != null) + { + strOff = setStringValue(bufPos, workstation, strOff, false); + flags |= NTLM.FlagWorkstationSupplied; + } + + // Initialize the header/flags + + initializeHeader(NTLM.Type1, flags); + + // Set the message length + + setLength(strOff); + } + + /** + * Set the message flags + * + * @param flags int + */ + protected void setFlags(int flags) + { + setIntValue( OffsetFlags, flags); + } + + /** + * Return the type 1 message as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("[Type1:0x"); + str.append(Integer.toHexString(getFlags())); + str.append(",Domain:"); + if ( hasDomain()) + str.append(getDomain()); + else + str.append(""); + str.append(",Wks:"); + + if ( hasWorkstation()) + str.append(getWorkstation()); + else + str.append(""); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/Type2NTLMMessage.java b/source/java/org/alfresco/filesys/server/auth/ntlm/Type2NTLMMessage.java new file mode 100644 index 0000000000..595e66ccdb --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/Type2NTLMMessage.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.filesys.util.HexDump; + +/** + * Type 2 NTLM Message Class + * + * @author GKSpencer + */ +public class Type2NTLMMessage extends NTLMMessage +{ + // Minimal type 2 message length + + public static final int MinimalMessageLength = 32; + + // Type 2 field offsets + + public static final int OffsetTarget = 12; + public static final int OffsetFlags = 20; + public static final int OffsetChallenge = 24; + public static final int OffsetContext = 32; + public static final int OffsetTargetInfo = 40; // optional + + /** + * Default constructor + */ + public Type2NTLMMessage() + { + super(); + } + + /** + * Class constructor + * + * @param buf byte[] + */ + public Type2NTLMMessage(byte[] buf) + { + super(buf, 0, buf.length); + } + + /** + * Class constructor + * + * @param buf byte[] + * @param offset int + * @param len int + */ + public Type2NTLMMessage(byte[] buf, int offset, int len) + { + super(buf, offset, len); + } + + /** + * Return the flags value + * + * @return int + */ + public int getFlags() + { + return getIntValue(OffsetFlags); + } + + /** + * Check if the target name has been set + * + * @return boolean + */ + public final boolean hasTarget() + { + return hasFlag(NTLM.FlagRequestTarget); + } + + /** + * Return the target name + * + * @return String + */ + public final String getTarget() + { + return getStringValue(OffsetTarget, hasFlag(NTLM.FlagNegotiateUnicode)); + } + + /** + * Return the challenge + * + * @return byte[] + */ + public final byte[] getChallenge() + { + return getRawBytes(OffsetChallenge, 8); + } + + /** + * Check if the optional context field is present + * + * @return boolean + */ + public final boolean hasContext() + { + return hasFlag(NTLM.FlagLocalCall); + } + + /** + * Return the context values + * + * @return int[] + */ + public final int[] getContext() + { + if ( hasContext() == false) + return null; + + int[] ctx = new int[2]; + + ctx[0] = getIntValue(OffsetContext); + ctx[1] = getIntValue(OffsetContext + 4); + + return ctx; + } + + /** + * Check if target information is present + * + * @return boolean + */ + public final boolean hasTargetInformation() + { + return hasFlag(NTLM.FlagTargetInfo); + } + + /** + * Return the target information + * + * @return List + */ + public final List getTargetInformation() + { + if ( hasTargetInformation() == false) + return null; + + // Get the target information block length and offset + + int tLen = getStringLength(OffsetTargetInfo); + int tOff = getStringOffset(OffsetTargetInfo); + + List tList = new ArrayList(); + if ( tLen == 0) + return tList; + + // Unpack the target information structures + + int typ = -1; + int slen = -1; + String name = null; + + while ( typ != 0) + { + // Unpack the details for the current target + + typ = getShortValue(tOff); + slen = getShortValue(tOff + 2); + + if ( slen > 0) + name = getRawString(tOff + 4, slen/2, true); + else + name = null; + + // Add the details to the list + + if ( typ != 0) + tList.add( new TargetInfo(typ, name)); + + // Update the data offset + + tOff += slen + 4; + } + + // Return the target list + + return tList; + } + + /** + * Build a type 2 message + * + * @param flags int + * @param target String + * @param challenge byte[] + * @param ctx byte[] + * @param tList List + */ + public final void buildType2(int flags, String target, byte[] challenge, int[] ctx, List tList) + { + // Initialize the header/flags + + initializeHeader(NTLM.Type2, flags); + + // Determine if strings are ASCII or Unicode + + boolean isUni = hasFlag(NTLM.FlagNegotiateUnicode); + + int strOff = OffsetTargetInfo; + if ( tList != null) + strOff += 8; + + // Pack the target name + + strOff = setStringValue(OffsetTarget, target, strOff, isUni); + + // Pack the challenge and context + + if ( challenge != null) + setRawBytes(OffsetChallenge, challenge); + else + zeroBytes(OffsetChallenge, 8); + + if ( ctx != null) + setRawInts(OffsetContext, ctx); + else + zeroBytes(OffsetContext, 8); + + // Pack the target information, if specified + + if ( tList != null) + { + // Clear the target information length and set the data offset + + setIntValue(OffsetTargetInfo, 0); + setIntValue(OffsetTargetInfo+4, strOff); + + int startOff = strOff; + + // Pack the target information structures + + for ( TargetInfo tInfo : tList) + { + // Pack the target information structure + + setShortValue(strOff, tInfo.isType()); + + int tLen = tInfo.getName().length(); + if ( isUni) + tLen *= 2; + setShortValue(strOff+2, tLen); + strOff = setRawString(strOff+4, tInfo.getName(), isUni); + } + + // Add the list terminator + + zeroBytes(strOff, 4); + strOff += 4; + + // Set the target information block length + + setShortValue(OffsetTargetInfo, strOff - startOff); + setShortValue(OffsetTargetInfo+2, strOff - startOff); + } + + // Set the message length + + setLength(strOff); + } + + /** + * Set the message flags + * + * @param flags int + */ + protected void setFlags(int flags) + { + setIntValue( OffsetFlags, flags); + } + + /** + * Return the type 2 message as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("[Type2:0x"); + str.append(Integer.toHexString(getFlags())); + str.append(",Target:"); + str.append(getTarget()); + str.append(",Ch:"); + str.append(HexDump.hexString(getChallenge())); + + if ( hasTargetInformation()) + { + List targets = getTargetInformation(); + + str.append(",TargInf:"); + for ( TargetInfo target : targets) + { + str.append(target); + } + } + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/ntlm/Type3NTLMMessage.java b/source/java/org/alfresco/filesys/server/auth/ntlm/Type3NTLMMessage.java new file mode 100644 index 0000000000..5089c32ebc --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/ntlm/Type3NTLMMessage.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.ntlm; + +import org.alfresco.filesys.util.HexDump; + +/** + * Type 3 NTLM Message Class + * + * @author GKSpencer + */ +public class Type3NTLMMessage extends NTLMMessage +{ + // Minimal type 3 message length + + public static final int MinimalMessageLength = 52; + + // Type 2 field offsets + + public static final int OffsetLMResponse = 12; + public static final int OffsetNTLMResponse = 20; + public static final int OffsetDomain = 28; + public static final int OffsetUserName = 36; + public static final int OffsetWorkstationName = 44; + public static final int OffsetDataMinimum = 52; + public static final int OffsetSessionKey = 52; // optional + public static final int OffsetFlags = 60; // optional + public static final int OffsetData = 64; + + // Flag to indicate if Unicode strings have been negotiated + + private boolean m_unicode; + + // Data block offset, used to indicate if session key and flags have been specified + + private int m_dataOffset = -1; + + /** + * Default constructor + */ + public Type3NTLMMessage() + { + super(); + } + + /** + * Class constructor + * + * @param buf byte[] + */ + public Type3NTLMMessage(byte[] buf) + { + super(buf, 0, buf.length); + } + + /** + * Class constructor + * + * @param buf byte[] + * @param offset int + * @param len int + * @param unicode boolean + */ + public Type3NTLMMessage(byte[] buf, int offset, int len, boolean unicode) + { + super(buf, offset, len); + + m_unicode = unicode; + } + + /** + * Return the flags value + * + * @return int + */ + public int getFlags() + { + return getIntValue(OffsetFlags); + } + + /** + * Return the LM password hash + * + * @return byte[] + */ + public final byte[] getLMHash() + { + return getByteValue(OffsetLMResponse); + } + + /** + * Return the NTLM password hash + * + * @return byte[] + */ + public final byte[] getNTLMHash() + { + return getByteValue(OffsetNTLMResponse); + } + + /** + * Return the domain name + * + * @return String + */ + public final String getDomain() + { + return getStringValue(OffsetDomain, hasFlag(NTLM.FlagNegotiateUnicode)); + } + + /** + * Return the user name + * + * @return String + */ + public final String getUserName() + { + return getStringValue(OffsetUserName, hasFlag(NTLM.FlagNegotiateUnicode)); + } + + /** + * Return the workstation name + * + * @return String + */ + public final String getWorkstation() + { + return getStringValue(OffsetWorkstationName, hasFlag(NTLM.FlagNegotiateUnicode)); + } + + /** + * Determine if the session key has been specified + * + * @return boolean + */ + public final boolean hasSessionKey() + { + return getShortValue(OffsetSessionKey) > 0; + } + + /** + * Return the session key, or null if the session key is not present + * + * @return byte[] + */ + public final byte[] getSessionKey() + { + if ( hasSessionKey() == false) + return null; + + // Get the session key bytes + + return getByteValue(OffsetSessionKey); + } + + /** + * Build a type 3 message + * + * @param lmHash byte[] + * @param ntlmHash byte[] + * @param domain String + * @param username String + * @param wksname String + * @param sessKey byte[] + * @param flags int + */ + public final void buildType3(byte[] lmHash, byte[] ntlmHash, String domain, String username, String wksname, + byte[] sessKey, int flags) + { + initializeHeader(NTLM.Type3, 0); + + // Set the data offset + + int dataOff = OffsetData; + + // Pack the domain, user and workstation names + + dataOff = setStringValue(OffsetDomain, domain, dataOff, m_unicode); + dataOff = setStringValue(OffsetUserName, username, dataOff, m_unicode); + dataOff = setStringValue(OffsetWorkstationName, wksname, dataOff, m_unicode); + + // Pack the LM and NTLM password hashes + + dataOff = setByteValue(OffsetLMResponse, lmHash, dataOff); + dataOff = setByteValue(OffsetNTLMResponse, ntlmHash, dataOff); + + // Pack the session key + + dataOff = setByteValue(OffsetSessionKey, sessKey, dataOff); + + // Make sure various flags are set + + int typ3flags = NTLM.FlagNegotiateNTLM + NTLM.FlagRequestTarget; + if ( m_unicode) + flags += NTLM.FlagNegotiateUnicode; + + // Pack the flags + + setIntValue(OffsetFlags, typ3flags); + + // Set the message length + + setLength(dataOff); + } + + /** + * Set the message flags + * + * @param flags int + */ + protected void setFlags(int flags) + { + setIntValue(OffsetFlags, flags); + } + + /** + * Find the data block offset + * + * @return int + */ + private final int findDataBlockOffset() + { + // Find the lowest data offset + // + // Check the LM hash + + int offset = getByteOffset(OffsetLMResponse); + + if ( m_dataOffset == -1 || offset < m_dataOffset) + m_dataOffset = offset; + + // Check the NTLM hash + + offset = getByteOffset(OffsetNTLMResponse); + if ( offset < m_dataOffset) + m_dataOffset = offset; + + // Check the domain name + + offset = getStringOffset(OffsetDomain); + if ( offset < m_dataOffset) + m_dataOffset = offset; + + // Check the user name + + offset = getStringOffset(OffsetUserName); + if ( offset < m_dataOffset) + m_dataOffset = offset; + + // Check the workstation + + offset = getStringOffset(OffsetWorkstationName); + if ( offset < m_dataOffset) + m_dataOffset = offset; + + // Return the new data offset + + return m_dataOffset; + } + + + /** + * Return the type 3 message as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("[Type3:"); + + str.append(",LM:"); + if ( getLMHash() != null) + str.append(HexDump.hexString(getLMHash())); + else + str.append(""); + + str.append(",NTLM:"); + if ( getNTLMHash() != null) + str.append(HexDump.hexString(getNTLMHash())); + else + str.append(""); + + str.append(",Dom:"); + str.append(getDomain()); + str.append(",User:"); + str.append(getUserName()); + str.append(",Wks:"); + str.append(getWorkstation()); + + if ( hasSessionKey()) + { + str.append(",SessKey:"); + str.append(HexDump.hexString(getSessionKey())); + str.append(",Flags:0x"); + str.append(Integer.toHexString(getFlags())); + } + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/AcegiPassthruAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/passthru/AcegiPassthruAuthenticator.java new file mode 100644 index 0000000000..ce7daf18c8 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/AcegiPassthruAuthenticator.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +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/AuthSessionFactory.java b/source/java/org/alfresco/filesys/server/auth/passthru/AuthSessionFactory.java new file mode 100644 index 0000000000..e6d0992817 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/AuthSessionFactory.java @@ -0,0 +1,929 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.passthru; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.alfresco.filesys.netbios.NetBIOSName; +import org.alfresco.filesys.netbios.NetBIOSNameList; +import org.alfresco.filesys.netbios.NetBIOSSession; +import org.alfresco.filesys.netbios.RFCNetBIOSProtocol; +import org.alfresco.filesys.server.auth.PasswordEncryptor; +import org.alfresco.filesys.smb.DataType; +import org.alfresco.filesys.smb.Dialect; +import org.alfresco.filesys.smb.DialectSelector; +import org.alfresco.filesys.smb.NetworkSession; +import org.alfresco.filesys.smb.PCShare; +import org.alfresco.filesys.smb.PacketType; +import org.alfresco.filesys.smb.Protocol; +import org.alfresco.filesys.smb.SMBException; +import org.alfresco.filesys.util.IPAddress; +import org.alfresco.filesys.util.StringList; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + *

+ * The AuthSessionFactory static class is used to create sessions to remote shared resources using + * the SMB/CIFS protocol. A PCShare object is used to specify the remote node and share details, as + * well as required access control details. + */ +public final class AuthSessionFactory +{ + + // Debug logging + + private static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); + + // Constants + + private static final int BROADCAST_LOOKUP_TIMEOUT = 4000; // ms + + // Default SMB dialect list + + private static DialectSelector m_defDialects; + + // Session index, used to make each session request call id unique + + private static int m_sessIdx = 1; + + // Local domain name, if known + + private static String m_localDomain = null; + + // Local domains browse master, if known + + private static String m_localBrowseMaster = null; + + // Default session packet buffer size + + private static int m_defPktSize = 4096 + RFCNetBIOSProtocol.HEADER_LEN; + + // List of local TCP/IP addresses + + private static InetAddress[] m_localAddrList; + + // Password encryptor + + private static PasswordEncryptor m_encryptor = new PasswordEncryptor(); + + static + { + + // Initialize the default dialect list + + m_defDialects = new DialectSelector(); + m_defDialects.AddDialect(Dialect.Core); + m_defDialects.AddDialect(Dialect.CorePlus); + m_defDialects.AddDialect(Dialect.DOSLanMan1); + m_defDialects.AddDialect(Dialect.DOSLanMan2); + m_defDialects.AddDialect(Dialect.LanMan1); + m_defDialects.AddDialect(Dialect.LanMan2); + m_defDialects.AddDialect(Dialect.LanMan2_1); + m_defDialects.AddDialect(Dialect.NT); + } + + // Default user name, password and domain used by methods that create their own connections. + + private static String m_defUserName = ""; + private static String m_defPassword = ""; + private static String m_defDomain = "?"; + + // Primary and secondary protocols to connect with + + private static int m_primaryProto = Protocol.TCPNetBIOS; + private static int m_secondaryProto = Protocol.NativeSMB; + + // NetBIOS port to connect to when setting up a new session. The default port is 139. + + private static int m_netbiosPort = RFCNetBIOSProtocol.PORT; + + // NetBIOS name scope + + private static String m_netBIOSScopeId = null; + + // Flag to enable extended security exchanges in the session setup + + private static boolean m_useExtendedSec = true; + + /** + * Build an SMB negotiate dialect packet. + * + * @param pkt SMBPacket to build the negotiate request + * @param dlct SMB dialects to negotiate + * @param pid Process id to be used by this new session + * @return StringList + */ + + private final static StringList BuildNegotiatePacket(SMBPacket pkt, DialectSelector dlct, int pid) + { + + // Initialize the SMB packet header fields + + pkt.setCommand(PacketType.Negotiate); + pkt.setProcessId(pid); + + // If the NT dialect is enabled set the Unicode flag in the request flags + + int flags2 = 0; + + if (dlct.hasDialect(Dialect.NT)) + flags2 += SMBPacket.FLG2_UNICODE; + + if ( useExtendedSecurity()) + flags2 += SMBPacket.FLG2_EXTENDEDSECURITY; + + pkt.setFlags2(flags2); + + // Build the SMB dialect list + + StringBuilder dia = new StringBuilder(); + StringList diaList = new StringList(); + + // Loop through all SMB dialect types and add the appropriate dialect strings + // to the negotiate packet. + + int d = Dialect.Core; + + while (d < Dialect.Max) + { + + // Check if the current dialect is selected + + if (dlct.hasDialect(d)) + { + + // Search the SMB dialect type string list and add all strings for the + // current dialect + + for (int i = 0; i < Dialect.NumberOfDialects(); i++) + { + + // Check if the current dialect string should be added to the list + + if (Dialect.DialectType(i) == d) + { + + // Get the current SMB dialect string + + String curDialect = Dialect.DialectString(i); + diaList.addString(curDialect); + + // Add the current SMB dialect type string to the negotiate packet + + dia.append(DataType.Dialect); + dia.append(curDialect); + dia.append((char) 0x00); + } + } + } + + // Update the current dialect type + + d++; + } + + // Copy the dialect strings to the SMB packet + + pkt.setBytes(dia.toString().getBytes()); + + // Return the dialect strings + + return diaList; + } + + /** + * Return the default SMB packet size + * + * @return Default SMB packet size to allocate. + */ + protected final static int DefaultPacketSize() + { + return m_defPktSize; + } + + /** + * Return the list of SMB dialects that will be negotiated when a new session is created. + * + * @return DialectSelector List of enabled SMB dialects. + */ + public final static DialectSelector getDefaultDialects() + { + return m_defDialects; + } + + /** + * Return the default domain name + * + * @return String + */ + public static String getDefaultDomain() + { + return m_defDomain; + } + + /** + * Return the default password. + * + * @return java.lang.String + */ + public static String getDefaultPassword() + { + return m_defPassword; + } + + /** + * Return the default user name. + * + * @return java.lang.String + */ + public static String getDefaultUserName() + { + return m_defUserName; + } + + /** + * Return the NetBIOS scope id, or null if not set + * + * @return String + */ + public static String getNetBIOSNameScope() + { + return m_netBIOSScopeId; + } + + /** + * Return the NetBIOS socket number that new sessions are connected to. + * + * @return int NetBIOS session socket number. + */ + public static int getNetBIOSPort() + { + return m_netbiosPort; + } + + /** + * Return the primary connection protocol (either Protocol.TCPNetBIOS or Protocol.NativeSMB) + * + * @return int + */ + public static final int getPrimaryProtocol() + { + return m_primaryProto; + } + + /** + * Return the secondary connection protocol (Protocol.TCPNetBIOS, Protocol.NativeSMB or + * Protocol.None) + * + * @return int + */ + public static final int getSecondaryProtocol() + { + return m_secondaryProto; + } + + /** + * Return the next session id + * + * @return int + */ + private static synchronized int getSessionId() + { + int sessId = m_sessIdx++ + (NetBIOSSession.getJVMIndex() * 100); + return sessId; + } + + /** + * Get the list of local TCP/IP addresses + * + * @return InetAddress[] + */ + private static synchronized InetAddress[] getLocalTcpipAddresses() + { + + // Get the list of local TCP/IP addresses + + if (m_localAddrList == null) + { + try + { + m_localAddrList = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName()); + } + catch (UnknownHostException ex) + { + } + } + + // Return the address list + + return m_localAddrList; + } + + /** + * Determine if the NetBIOS name scope is set + * + * @return boolean + */ + public final static boolean hasNetBIOSNameScope() + { + return m_netBIOSScopeId != null ? true : false; + } + + /** + * Determine if extended security exchanges are enabled + * + * @return boolean + */ + public static final boolean useExtendedSecurity() + { + return m_useExtendedSec; + } + + /** + * Open a session to a remote server, negotiate an SMB dialect and get the returned challenge + * key. Returns an AuthenticateSession which can then be used to provide passthru + * authentication. + * + * @param shr Remote server share and access control details. + * @param tmo Timeout value in milliseconds + * @return AuthenticateSession for the new session, else null. + * @exception java.io.IOException If an I/O error occurs. + * @exception java.net.UnknownHostException Remote node is unknown. + * @exception SMBException Failed to setup a new session. + */ + public static AuthenticateSession OpenAuthenticateSession(PCShare shr, int tmo) throws java.io.IOException, + java.net.UnknownHostException, SMBException + { + + // Open an authentication session + + return OpenAuthenticateSession(shr, tmo, null); + } + + /** + * Open a session to a remote server, negotiate an SMB dialect and get the returned challenge + * key. Returns an AuthenticateSession which can then be used to provide passthru + * authentication. + * + * @param shr Remote server share and access control details. + * @param tmo Timeout value in milliseconds + * @param dia SMB dialects to negotiate for this session. + * @return AuthenticateSession for the new session, else null. + * @exception java.io.IOException If an I/O error occurs. + * @exception java.net.UnknownHostException Remote node is unknown. + * @exception SMBException Failed to setup a new session. + */ + public static AuthenticateSession OpenAuthenticateSession(PCShare shr, int tmo, DialectSelector dia) + throws java.io.IOException, java.net.UnknownHostException, SMBException + { + + // Build a unique caller name + + int pid = getSessionId(); + + StringBuilder nameBuf = new StringBuilder(InetAddress.getLocalHost().getHostName() + "_" + pid); + String localName = nameBuf.toString(); + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("New auth session from " + localName + " to " + shr.toString()); + + // Connect to the requested server + + NetworkSession netSession = null; + + switch (getPrimaryProtocol()) + { + + // NetBIOS connection + + case Protocol.TCPNetBIOS: + netSession = connectNetBIOSSession(shr.getNodeName(), localName, tmo); + break; + + // Native SMB connection + + case Protocol.NativeSMB: + netSession = connectNativeSMBSession(shr.getNodeName(), localName, tmo); + break; + } + + // If the connection was not made using the primary protocol try the secondary protocol, if + // configured + + if (netSession == null) + { + + // Try the secondary protocol + + switch (getSecondaryProtocol()) + { + + // NetBIOS connection + + case Protocol.TCPNetBIOS: + netSession = connectNetBIOSSession(shr.getNodeName(), localName, tmo); + break; + + // Native SMB connection + + case Protocol.NativeSMB: + netSession = connectNativeSMBSession(shr.getNodeName(), localName, tmo); + break; + } + } + + // Check if we connected to the remote host + + if (netSession == null) + throw new IOException("Failed to connect to host, " + shr.getNodeName()); + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("Connected session, protocol : " + netSession.getProtocolName()); + + // Build a protocol negotiation SMB packet, and send it to the remote + // file server. + + SMBPacket pkt = new SMBPacket(); + DialectSelector selDialect = dia; + + if (selDialect == null) + { + + // Use the default SMB dialect list + + selDialect = new DialectSelector(); + selDialect.copyFrom(m_defDialects); + } + + // Build the negotiate SMB dialect packet and exchange with the remote server + + StringList diaList = BuildNegotiatePacket(pkt, selDialect, pid); + pkt.ExchangeLowLevelSMB(netSession, pkt, true); + + // Determine the selected SMB dialect + + String diaStr = diaList.getStringAt(pkt.getParameter(0)); + int dialectId = Dialect.DialectType(diaStr); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("SessionFactory: Negotiated SMB dialect " + diaStr); + + if (dialectId == Dialect.Unknown) + throw new java.io.IOException("Unknown SMB dialect"); + + // Create the authenticate session + + AuthenticateSession authSess = new AuthenticateSession(shr, netSession, dialectId, pkt); + return authSess; + } + + /** + * Set the default domain. + * + * @param domain String + */ + public static void setDefaultDomain(String domain) + { + m_defDomain = domain; + } + + /** + * Set the default password. + * + * @param pwd java.lang.String + */ + public static void setDefaultPassword(String pwd) + { + m_defPassword = pwd; + } + + /** + * Set the default user name. + * + * @param user java.lang.String + */ + public static void setDefaultUserName(String user) + { + m_defUserName = user; + } + + /** + * Enable/disable the use of extended security exchanges + * + * @param ena boolean + */ + public static final void setExtendedSecurity(boolean ena) + { + m_useExtendedSec = ena; + } + + /** + * Set the NetBIOS socket number to be used when setting up new sessions. The default socket is + * 139. + * + * @param port int + */ + public static void setNetBIOSPort(int port) + { + m_netbiosPort = port; + } + + /** + * Set the NetBIOS scope id + * + * @param scope String + */ + public static void setNetBIOSNameScope(String scope) + { + if (scope != null && scope.startsWith(".")) + m_netBIOSScopeId = scope.substring(1); + else + m_netBIOSScopeId = scope; + } + + /** + * Set the protocol connection order + * + * @param pri Primary connection protocol + * @param sec Secondary connection protocol, or none + * @return boolean + */ + public static final boolean setProtocolOrder(int pri, int sec) + { + + // Primary protocol must be specified + + if (pri != Protocol.TCPNetBIOS && pri != Protocol.NativeSMB) + return false; + + // Primary and secondary must be different + + if (pri == sec) + return false; + + // Save the settings + + m_primaryProto = pri; + m_secondaryProto = sec; + + return true; + } + + /** + * Set the subnet mask string for network broadcast requests If the subnet mask is not set a + * default broadcast mask for the TCP/IP address class will be used. + * + * @param subnet Subnet mask string, in 'nnn.nnn.nnn.nnn' format. + */ + public final static void setSubnetMask(String subnet) + { + NetBIOSSession.setSubnetMask(subnet); + } + + /** + * Setup the default SMB dialects to be negotiated when creating new sessions. + */ + private static void SetupDefaultDialects() + { + + // Initialize the default dialect list + + if (m_defDialects != null) + m_defDialects = new DialectSelector(); + else + m_defDialects.ClearAll(); + + // Always enable core protocol + + m_defDialects.AddDialect(Dialect.Core); + m_defDialects.AddDialect(Dialect.CorePlus); + m_defDialects.AddDialect(Dialect.DOSLanMan1); + m_defDialects.AddDialect(Dialect.DOSLanMan2); + m_defDialects.AddDialect(Dialect.LanMan1); + m_defDialects.AddDialect(Dialect.LanMan2); + m_defDialects.AddDialect(Dialect.LanMan2_1); + m_defDialects.AddDialect(Dialect.NT); + } + + /** + * Connect a NetBIOS network session + * + * @param toName Host name/address to connect to + * @param fromName Local host name/address + * @param tmo Timeout in seconds + * @return NetworkSession + * @exception IOException If a network error occurs + */ + private static final NetworkSession connectNetBIOSSession(String toName, String fromName, int tmo) + throws IOException + { + + // Connect to the requested server + + NetBIOSSession nbSession = new NetBIOSSession(tmo, getNetBIOSPort()); + + // Check if the remote host is specified as a TCP/IP address + + String toAddr = null; + NetBIOSName nbName = null; + + if (IPAddress.isNumericAddress(toName)) + { + + try + { + + // Get a list of NetBIOS names from the remote host + + toAddr = toName; + NetBIOSNameList nameList = NetBIOSSession.FindNamesForAddress(toAddr); + + // Find the server service + + nbName = nameList.findName(NetBIOSName.FileServer, false); + if (nbName == null) + throw new IOException("Server service not running"); + + // Set the remote name + + toName = nbName.getName(); + } + catch (UnknownHostException ex) + { + return null; + } + } + else + { + IOException savedException = null; + + try + { + + // Find the remote host and get a list of the network addresses it is using + + nbName = NetBIOSSession.FindName(toName, NetBIOSName.FileServer, 500); + } + catch (IOException ex) + { + savedException = ex; + } + + // If the NetBIOS name was not found then check if the local system has the name + + if (nbName == null) + { + + // Get a list of NetBIOS names for the local system + + NetBIOSNameList localList = NetBIOSSession.FindNamesForAddress(InetAddress.getLocalHost() + .getHostAddress()); + if (localList != null) + { + nbName = localList.findName(toName, NetBIOSName.FileServer, false); + if (nbName != null) + nbName.addIPAddress(InetAddress.getLocalHost().getAddress()); + else + throw savedException; + } + } + } + + // Check if the NetBIOS name scope has been set, if so then update the names to add the + // scope id + + if (hasNetBIOSNameScope()) + { + + // Add the NetBIOS scope id to the to/from NetBIOS names + + toName = toName + "." + getNetBIOSNameScope(); + fromName = fromName + "." + getNetBIOSNameScope(); + } + + // If the NetBIOS name has more than one TCP/IP address then find the best match for the + // client and + // try to connect on that address first, if that fails then we will have to try each address + // in turn. + + if (nbName.numberOfAddresses() > 1) + { + + // Get the local TCP/IP address list and search for a best match address to connect to + // the server on + + InetAddress[] addrList = getLocalTcpipAddresses(); + int addrIdx = nbName.findBestMatchAddress(addrList); + + if (addrIdx != -1) + { + + try + { + + // Get the server IP address + + String ipAddr = nbName.getIPAddressString(addrIdx); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Server is multi-homed, trying to connect to " + ipAddr); + + // Open the session to the remote host + + nbSession.Open(toName, fromName, ipAddr); + + // Check if the session is connected + + if (nbSession.isConnected() == false) + { + + // Close the session + + try + { + nbSession.Close(); + } + catch (Exception ex) + { + } + } + else if (logger.isDebugEnabled() && nbSession.isConnected()) + logger.debug("Connected to address " + ipAddr); + } + catch (IOException ex) + { + } + } + } + + // DEBUG + + if (logger.isDebugEnabled() && nbSession.isConnected() == false + && nbName.numberOfAddresses() > 1) + logger.debug("Server is multi-homed, trying all addresses"); + + // Loop through the available addresses for the remote file server until we get a successful + // connection, or all addresses have been used + + IOException lastException = null; + int addrIdx = 0; + + while (nbSession.isConnected() == false && addrIdx < nbName.numberOfAddresses()) + { + + try + { + + // Get the server IP address + + String ipAddr = nbName.getIPAddressString(addrIdx++); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Trying address " + ipAddr); + + // Open the session to the remote host + + nbSession.Open(toName, fromName, ipAddr); + + // Check if the session is connected + + if (nbSession.isConnected() == false) + { + + // Close the session + + try + { + nbSession.Close(); + } + catch (Exception ex) + { + } + } + else if (logger.isDebugEnabled() && nbSession.isConnected()) + logger.debug("Connected to address " + ipAddr); + } + catch (IOException ex) + { + + // Save the last exception + + lastException = ex; + } + } + + // Check if the session is connected + + if (nbSession.isConnected() == false) + { + + // If there is a saved exception rethrow it + + if (lastException != null) + throw lastException; + + // Indicate that the session was not connected + + return null; + } + + // Return the network session + + return nbSession; + } + + /** + * Connect a native SMB network session + * + * @param toName Host name/address to connect to + * @param fromName Local host name/address + * @param tmo Timeout in seconds + * @return NetworkSession + * @exception IOException If a network error occurs + */ + private static final NetworkSession connectNativeSMBSession(String toName, String fromName, int tmo) + throws IOException + { + + // Connect to the requested server + + TcpipSMBNetworkSession tcpSession = new TcpipSMBNetworkSession(tmo); + + try + { + + // Open the session + + tcpSession.Open(toName, fromName, null); + + // Check if the session is connected + + if (tcpSession.isConnected() == false) + { + + // Close the session + + try + { + tcpSession.Close(); + } + catch (Exception ex) + { + } + + // Return a null session + + return null; + } + } + catch (Exception ex) + { + try + { + tcpSession.Close(); + } + catch (Exception ex2) + { + } + tcpSession = null; + } + + // Return the network session + + return tcpSession; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java b/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java new file mode 100644 index 0000000000..2ae43567fe --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/AuthenticateSession.java @@ -0,0 +1,1647 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.passthru; + +import java.io.IOException; + +import org.alfresco.filesys.netbios.RFCNetBIOSProtocol; +import org.alfresco.filesys.server.auth.PasswordEncryptor; +import org.alfresco.filesys.server.auth.ntlm.NTLM; +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.smb.Capability; +import org.alfresco.filesys.smb.Dialect; +import org.alfresco.filesys.smb.NTTime; +import org.alfresco.filesys.smb.NetworkSession; +import org.alfresco.filesys.smb.PCShare; +import org.alfresco.filesys.smb.PacketType; +import org.alfresco.filesys.smb.SMBDate; +import org.alfresco.filesys.smb.SMBException; +import org.alfresco.filesys.smb.SMBStatus; +import org.alfresco.filesys.util.DataPacker; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Authenticate Session Class + *

+ * Used for passthru authentication mechanisms. + */ +public class AuthenticateSession +{ + + // Debug logging + + private static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); + + // Default packet size + + private static final int DefaultPacketSize = 1024; + + // Session security mode + + public static final int SecurityModeUser = 1; + public static final int SecurityModeShare = 2; + + // Tree identifier that indicates that the disk session has been closed + + protected final static int Closed = -1; + + // Default SMB packet size to allocate + + public static final int DEFAULT_BUFSIZE = 4096; + + // SMB dialect id and string for this session + + private int m_dialect; + private String m_diaStr; + + // Network session + + private NetworkSession m_netSession; + + // SMB packet for protocol exhanges + + protected SMBPacket m_pkt; + + // Default packet flags + + private int m_defFlags = SMBPacket.FLG_CASELESS; + private int m_defFlags2 = SMBPacket.FLG2_LONGFILENAMES; + + // Server connection details + + private PCShare m_remoteShr; + + // Domain name + + private String m_domain; + + // Remote operating system and LAN manager type + + private String m_srvOS; + private String m_srvLM; + + // Security mode (user or share) + + private int m_secMode; + + // Challenge encryption key + + private byte[] m_encryptKey; + + // SMB session information + + private int m_sessIdx; + private int m_userId; + private int m_processId; + + // Tree identifier for this connection + + protected int m_treeid; + + // Device type that this session is connected to + + private int m_devtype; + + // Maximum transmit buffer size allowed + + private int m_maxPktSize; + + // Session capabilities + + private int m_sessCaps; + + // Maximum virtual circuits allowed on this session, and maximum multiplxed read/writes + + private int m_maxVCs; + private int m_maxMPX; + + // Indicate if the session was created as a guest rather than using the supplied + // username/password + + private boolean m_guest; + + // Flag to indicate extended security exchange is being used + + private boolean m_extendedSec; + + // Server GUID, if using extended security + + private byte[] m_serverGUID; + + // Type 2 security blob from the server + + private Type2NTLMMessage m_type2Msg; + + // Global session id + + private static int m_sessionIdx = 1; + + // Multiplex id + + private static int m_multiplexId = 1; + + /** + * Class constructor + * + * @param shr PCShare + * @param sess NetworkSession + * @param dialect int + * @param pkt SMBPacket + * @exception IOException If a network error occurs + * @eception SMBException If a CIFS error occurs + */ + protected AuthenticateSession(PCShare shr, NetworkSession sess, int dialect, SMBPacket pkt) throws IOException, SMBException + { + + // Set the SMB dialect for this session + + m_dialect = dialect; + + // Save the remote share details + + m_remoteShr = shr; + + // Allocate a unique session index + + m_sessIdx = getNextSessionId(); + + // Allocate an SMB protocol packet + + m_pkt = pkt; + if (pkt == null) + m_pkt = new SMBPacket(DEFAULT_BUFSIZE); + + // Save the session and packet + + setSession(sess); + + // Extract the details from the negotiate response packet + + processNegotiateResponse(); + } + + /** + * Allocate an SMB packet for this session. The preferred packet size is specified, if a smaller + * buffer size has been negotiated a smaller SMB packet will be returned. + * + * @param pref Preferred SMB packet size + * @return Allocated SMB packet + */ + protected final SMBPacket allocatePacket(int pref) + { + + // Check if the preferred size is larger than the maximum allowed packet + // size for this session. + + if (pref > m_maxPktSize) + return new SMBPacket(m_maxPktSize + RFCNetBIOSProtocol.HEADER_LEN); + + // Return the preferred SMB packet size + + return new SMBPacket(pref + RFCNetBIOSProtocol.HEADER_LEN); + } + + /** + * Determine if the session supports extended security + * + * @return true if this session supports extended security, else false + */ + public final boolean supportsExtendedSecurity() + { + return (m_sessCaps & Capability.ExtendedSecurity) != 0 ? true : false; + } + + /** + * Determine if the session supports raw mode read/writes + * + * @return true if this session supports raw mode, else false + */ + public final boolean supportsRawMode() + { + return (m_sessCaps & Capability.RawMode) != 0 ? true : false; + } + + /** + * Determine if the session supports Unicode + * + * @return boolean + */ + public final boolean supportsUnicode() + { + return (m_sessCaps & Capability.Unicode) != 0 ? true : false; + } + + /** + * Determine if the session supports large files (ie. 64 bit file offsets) + * + * @return boolean + */ + public final boolean supportsLargeFiles() + { + return (m_sessCaps & Capability.LargeFiles) != 0 ? true : false; + } + + /** + * Determine if the session supports NT specific SMBs + * + * @return boolean + */ + public final boolean supportsNTSmbs() + { + return (m_sessCaps & Capability.NTSMBs) != 0 ? true : false; + } + + /** + * Determine if the session supports RPC API requests + * + * @return boolean + */ + public final boolean supportsRPCAPIs() + { + return (m_sessCaps & Capability.RemoteAPIs) != 0 ? true : false; + } + + /** + * Determine if the session supports NT status codes + * + * @return boolean + */ + public final boolean supportsNTStatusCodes() + { + return (m_sessCaps & Capability.NTStatus) != 0 ? true : false; + } + + /** + * Determine if the session supports level 2 oplocks + * + * @return boolean + */ + public final boolean supportsLevel2Oplocks() + { + return (m_sessCaps & Capability.Level2Oplocks) != 0 ? true : false; + } + + /** + * Determine if the session supports lock and read + * + * @return boolean + */ + public final boolean supportsLockAndRead() + { + return (m_sessCaps & Capability.LockAndRead) != 0 ? true : false; + } + + /** + * Determine if the session supports NT find + * + * @return boolean + */ + public final boolean supportsNTFind() + { + return (m_sessCaps & Capability.NTFind) != 0 ? true : false; + } + + /** + * Close this connection with the remote server. + * + * @exception java.io.IOException If an I/O error occurs. + * @exception SMBException If an SMB level error occurs + */ + public void CloseSession() throws java.io.IOException, SMBException + { + + // If the session is valid then hangup the session + + if (isActive()) + { + + // Close the network session + + m_netSession.Close(); + + // Clear the session + + m_netSession = null; + } + } + + /** + * Return the default flags settings for this session + * + * @return int + */ + public final int getDefaultFlags() + { + return m_defFlags; + } + + /** + * Return the default flags2 settings for this session + * + * @return int + */ + public final int getDefaultFlags2() + { + return m_defFlags2; + } + + /** + * Get the device type that this session is connected to. + * + * @return Device type for this session. + */ + public final int getDeviceType() + { + return m_devtype; + } + + /** + * Get the SMB dialect property + * + * @return SMB dialect that this session has negotiated. + */ + public final int getDialect() + { + return m_dialect; + } + + /** + * Get the SMB dialect string + * + * @return SMB dialect string for this session. + */ + public final String getDialectString() + { + return m_diaStr; + } + + /** + * Get the servers primary domain name + * + * @return Servers primary domain name, if knwon, else null. + */ + public final String getDomain() + { + return m_domain; + } + + /** + * Determine if there is a challenge encryption key + * + * @return boolean + */ + public final boolean hasEncryptionKey() + { + return m_encryptKey != null ? true : false; + } + + /** + * Return the cahllenge encryption key + * + * @return byte[] + */ + public final byte[] getEncryptionKey() + { + return m_encryptKey; + } + + /** + * Get the servers LAN manager type + * + * @return Servers LAN manager type, if known, else null. + */ + public final String getLANManagerType() + { + return m_srvLM; + } + + /** + * Get the maximum number of multiplxed requests that are allowed + * + * @return int + */ + public final int getMaximumMultiplexedRequests() + { + return m_maxMPX; + } + + /** + * Get the maximum packet size allowed for this session + * + * @return Maximum packet size, in bytes. + */ + public final int getMaximumPacketSize() + { + return m_maxPktSize; + } + + /** + * Get the maximum virtual circuits allowed on this session + * + * @return int + */ + public final int getMaximumVirtualCircuits() + { + return m_maxVCs; + } + + /** + * Get the next multiplex id to uniquely identify a transaction + * + * @return Unique multiplex id for a transaction + */ + public final synchronized int getNextMultiplexId() + { + return m_multiplexId++; + } + + /** + * Get the next session id + * + * @return int + */ + protected final synchronized int getNextSessionId() + { + return m_sessionIdx++; + } + + /** + * Get the servers operating system type + * + * @return Servers operating system, if known, else null. + */ + public final String getOperatingSystem() + { + return m_srvOS; + } + + /** + * Get the remote share password string + * + * @return Remote share password string + */ + public final String getPassword() + { + return m_remoteShr.getPassword(); + } + + /** + * Get the remote share details for this session + * + * @return PCShare information for this session + */ + public final PCShare getPCShare() + { + return m_remoteShr; + } + + /** + * Return the security mode of the session (user or share) + * + * @return int + */ + public final int getSecurityMode() + { + return m_secMode; + } + + /** + * Get the remote server name + * + * @return Remote server name + */ + public final String getServer() + { + return m_remoteShr.getNodeName(); + } + + /** + * Access the associated network session + * + * @return NetworkSession that the SMB session is using + */ + public final NetworkSession getSession() + { + return m_netSession; + } + + /** + * Determine if the session has an associated type2 NTLM security blob + * + * @return boolean + */ + public final boolean hasType2NTLMMessage() + { + return m_type2Msg != null ? true : false; + } + + /** + * Return the type2 NTLM security blob that was received from the authentication server + * + * @return Type2NTLMMessage + */ + public final Type2NTLMMessage getType2NTLMMessage() + { + return m_type2Msg; + } + + /** + * Return the session capability flags. + * + * @return int + */ + public final int getCapabilities() + { + return m_sessCaps; + } + + /** + * Get the process id for this session + * + * @return int + */ + public final int getProcessId() + { + return m_processId; + } + + /** + * Get the session identifier property + * + * @return Session identifier + */ + public final int getSessionId() + { + return m_sessIdx; + } + + /** + * Get the remote share name + * + * @return Remote share name string + */ + public final String getShareName() + { + return m_remoteShr.getShareName(); + } + + /** + * Get the connected tree identifier. + * + * @return Tree identifier. + */ + public final int getTreeId() + { + return m_treeid; + } + + /** + * Return the assigned use id for this SMB session + * + * @return Assigned user id + */ + public final int getUserId() + { + return m_userId; + } + + /** + * Get the remote share user name string + * + * @return Remote share user name string + */ + public final String getUserName() + { + return m_remoteShr.getUserName(); + } + + /** + * Check if there is data available in the network receive buffer + * + * @return boolean + * @exception IOException + */ + public final boolean hasDataAvailable() throws IOException + { + return m_netSession.hasData(); + } + + /** + * Determine if the session is valid, ie. still open. + * + * @return true if the session is still active, else false. + */ + public final boolean isActive() + { + return (m_netSession == null) ? false : true; + } + + /** + * Determine if the session has been created as a guest logon + * + * @return boolean + */ + public final boolean isGuest() + { + return m_guest; + } + + /** + * Determine if the Unicode flag is enabled + * + * @return boolean + */ + public final boolean isUnicode() + { + return (m_defFlags2 & SMBPacket.FLG2_UNICODE) != 0 ? true : false; + } + + /** + * Determine if extended security exchanges are being used + * + * @return boolean + */ + public final boolean isUsingExtendedSecurity() + { + return m_extendedSec; + } + + /** + * Send a single echo request to the server + * + * @throws java.io.IOException + * @throws SMBException + */ + public final void pingServer() throws java.io.IOException, SMBException + { + + // Send a single echo request to the server + + pingServer(1); + } + + /** + * Send an echo request to the server + * + * @param cnt Number of packets to echo from the remote server + * @exception java.io.IOException If an I/O error occurs + * @exception SMBException SMB error occurred + */ + public final void pingServer(int cnt) throws java.io.IOException, SMBException + { + + // Build a server ping SMB packet + + m_pkt.setCommand(PacketType.Echo); + m_pkt.setFlags(0); + m_pkt.setTreeId(getTreeId()); + m_pkt.setUserId(getUserId()); + m_pkt.setProcessId(getProcessId()); + m_pkt.setMultiplexId(1); + + // Set the parameter words + + m_pkt.setParameterCount(1); + m_pkt.setParameter(0, cnt); // number of packets that the server should return + String echoStr = "ECHO"; + m_pkt.setBytes(echoStr.getBytes()); + + // Send the echo request + + m_pkt.SendSMB(this); + + // Receive the reply packets, if any + + while (cnt > 0) + { + + // Receive a reply packet + + m_pkt.ReceiveSMB(this); + + // Decrement the reply counter + + cnt--; + } + } + + /** + * Set the default SMB packet flags for this session + * + * @param flg int + */ + protected final void setDefaultFlags(int flg) + { + m_defFlags = flg; + } + + /** + * Set the SMB packet default flags2 for this session + * + * @param flg2 int + */ + protected final void setDefaultFlags2(int flg2) + { + m_defFlags2 = flg2; + } + + /** + * Set the device type for this session. + * + * @param dev Device type for this session. + */ + protected final void setDeviceType(int dev) + { + m_devtype = dev; + } + + /** + * Set the dialect for this session + * + * @param dia SMB dialect that this session is using. + */ + protected final void setDialect(int dia) + { + m_dialect = dia; + } + + /** + * Set the dialect string for this session + * + * @param dia SMB dialect string + */ + protected final void setDialectString(String dia) + { + m_diaStr = dia; + } + + /** + * Set the remote servers primary domain name + * + * @param dom Servers primary domain name. + */ + protected final void setDomain(String dom) + { + m_domain = dom; + } + + /** + * Set the encryption key + * + * @param key byte[] + */ + public final void setEncryptionKey(byte[] key) + { + + // Set the challenge response encryption key + + m_encryptKey = key; + } + + /** + * Set the guest status for the session + * + * @param sts boolean + */ + protected final void setGuest(boolean sts) + { + m_guest = sts; + } + + /** + * Set the remote servers LAN manager type + * + * @param lm Servers LAN manager type string. + */ + protected final void setLANManagerType(String lm) + { + m_srvLM = lm; + } + + /** + * Set the maximum number of multiplexed requests allowed + * + * @param maxMulti int + */ + protected final void setMaximumMultiplexedRequests(int maxMulti) + { + m_maxMPX = maxMulti; + } + + /** + * Set the maximum packet size allowed on this session + * + * @param siz Maximum allowed packet size. + */ + protected final void setMaximumPacketSize(int siz) + { + m_maxPktSize = siz; + } + + /** + * Set the maximum number of virtual circuits allowed on this session + * + * @param maxVC int + */ + protected final void setMaximumVirtualCircuits(int maxVC) + { + m_maxVCs = maxVC; + } + + /** + * Set the remote servers operating system type + * + * @param os Servers operating system type string. + */ + protected final void setOperatingSystem(String os) + { + m_srvOS = os; + } + + /** + * Set the remote share password + * + * @param pwd Remtoe share password string. + */ + protected final void setPassword(String pwd) + { + m_remoteShr.setPassword(pwd); + } + + /** + * Set the session security mode (user or share) + * + * @param secMode int + */ + public final void setSecurityMode(int secMode) + { + m_secMode = secMode; + } + + /** + * Set the remote server name + * + * @param srv Server name string + */ + protected final void setServer(String srv) + { + m_remoteShr.setNodeName(srv); + } + + /** + * Set the network session that this SMB session is associated with + * + * @param netSess Network session that this SMB session is to be associated with. + */ + protected final void setSession(NetworkSession netSess) + { + m_netSession = netSess; + } + + /** + * Set the session capability flags + * + * @param flg Capability flags. + */ + protected final void setCapabilities(int caps) + { + m_sessCaps = caps; + } + + /** + * Set the remote share name + * + * @param shr Remote share name string + */ + protected final void setShareName(String shr) + { + m_remoteShr.setShareName(shr); + } + + /** + * Set the process id for this session + * + * @param id + */ + protected final void setProcessId(int id) + { + m_processId = id; + } + + /** + * Set the connected tree identifier for this session. + * + * @param id Tree identifier for this session. + */ + protected final void setTreeId(int id) + { + m_treeid = id; + } + + /** + * Set the user identifier for this session + * + * @param uid User identifier + */ + protected final void setUserId(int uid) + { + m_userId = uid; + } + + /** + * Set the remote share user name + * + * @param user Remote share user name string + */ + protected final void setUserName(String user) + { + m_remoteShr.setUserName(user); + } + + /** + * Output the session details as a string + * + * @return Session details string + */ + public String toString() + { + StringBuffer str = new StringBuffer(); + + str.append("[\\\\"); + str.append(getServer()); + str.append("\\"); + str.append(getShareName()); + str.append(":"); + str.append(Dialect.DialectTypeString(m_dialect)); + str.append(",UserId="); + str.append(getUserId()); + str.append("]"); + + return str.toString(); + } + + /** + * Perform a session setup to create a session on the remote server validating the user. + * + * @param userName String + * @param ascPwd ASCII password hash + * @param uniPwd Unicode password hash + * @exception IOException If a network error occurs + * @exception SMBException If a CIFS error occurs + */ + public final void doSessionSetup(String userName, byte[] ascPwd, byte[] uniPwd) throws IOException, SMBException + { + doSessionSetup(null, userName, null, ascPwd, uniPwd); + } + + /** + * Perform a session using the type3 NTLM response received from the client + * + * @param type3 Type3NTLMMessage + * @exception IOException If a network error occurs + * @exception SMBException If a CIFS error occurs + */ + public final void doSessionSetup(Type3NTLMMessage type3Msg) throws IOException, SMBException + { + doSessionSetup(type3Msg.getDomain(), type3Msg.getUserName(), type3Msg.getWorkstation(), + type3Msg.getLMHash(), type3Msg.getNTLMHash()); + } + + /** + * Perform a session setup to create a session on the remote server validating the user. + * + * @param domain String + * @param userName String + * @param wksName String + * @param ascPwd ASCII password hash + * @param uniPwd Unicode password hash + * @exception IOException If a network error occurs + * @exception SMBException If a CIFS error occurs + */ + public final void doSessionSetup(String domain, String userName, String wksName, + byte[] ascPwd, byte[] uniPwd) throws IOException, SMBException + { + // Check if we are using extended security + + if ( isUsingExtendedSecurity()) + { + // Run the second phase of the extended security session setup + + doExtendedSessionSetupPhase2(domain, userName, wksName, ascPwd, uniPwd); + return; + } + + // Create a session setup packet + + SMBPacket pkt = new SMBPacket(); + + pkt.setCommand(PacketType.SessionSetupAndX); + + // Check if the negotiated SMB dialect is NT LM 1.2 or an earlier dialect + + if (getDialect() == Dialect.NT) + { + + // NT LM 1.2 SMB dialect + + pkt.setParameterCount(13); + pkt.setAndXCommand(0xFF); // no secondary command + pkt.setParameter(1, 0); // offset to next command + pkt.setParameter(2, DefaultPacketSize); + pkt.setParameter(3, 1); + pkt.setParameter(4, 0); // virtual circuit number + pkt.setParameterLong(5, 0); // session key + + // Set the share password length(s) + + pkt.setParameter(7, ascPwd != null ? ascPwd.length : 0); // ANSI password length + pkt.setParameter(8, uniPwd != null ? uniPwd.length : 0); // Unicode password length + + pkt.setParameter(9, 0); // reserved, must be zero + pkt.setParameter(10, 0); // reserved, must be zero + + // Send the client capabilities + + int caps = Capability.LargeFiles + Capability.Unicode + Capability.NTSMBs + Capability.NTStatus + + Capability.RemoteAPIs; + + // Set the client capabilities + + pkt.setParameterLong(11, caps); + + // Get the offset to the session setup request byte data + + int pos = pkt.getByteOffset(); + pkt.setPosition(pos); + + // Store the ASCII password hash, if specified + + if (ascPwd != null) + pkt.packBytes(ascPwd, ascPwd.length); + + // Store the Unicode password hash, if specified + + if (uniPwd != null) + pkt.packBytes(uniPwd, uniPwd.length); + + // Pack the account/client details + + pkt.packString(userName, false); + + // Check if the share has a domain, if not then use the default domain string + + if (getPCShare().hasDomain()) + pkt.packString(getPCShare().getDomain(), false); + else + pkt.packString("?", false); + + pkt.packString("Java VM", false); + pkt.packString("JLAN", false); + + // Set the packet length + + pkt.setByteCount(pkt.getPosition() - pos); + } + else + { + + // Earlier SMB dialect + + pkt.setUserId(1); + + pkt.setParameterCount(10); + pkt.setAndXCommand(0xFF); + pkt.setParameter(1, 0); + pkt.setParameter(2, DefaultPacketSize); + pkt.setParameter(3, 1); + pkt.setParameter(4, 0); + pkt.setParameter(5, 0); + pkt.setParameter(6, 0); + pkt.setParameter(7, ascPwd != null ? ascPwd.length : 0); + pkt.setParameter(8, 0); + pkt.setParameter(9, 0); + + // Put the password into the SMB packet + + byte[] buf = pkt.getBuffer(); + int pos = pkt.getByteOffset(); + + if (ascPwd != null) + { + for (int i = 0; i < ascPwd.length; i++) + buf[pos++] = ascPwd[i]; + } + + // Build the account/client details + + StringBuffer clbuf = new StringBuffer(); + + clbuf.append(getPCShare().getUserName()); + clbuf.append((char) 0x00); + + // Check if the share has a domain, if not then use the unknown domain string + + if (getPCShare().hasDomain()) + clbuf.append(getPCShare().getDomain()); + else + clbuf.append("?"); + clbuf.append((char) 0x00); + + clbuf.append("Java VM"); + clbuf.append((char) 0x00); + + clbuf.append("JLAN"); + clbuf.append((char) 0x00); + + // Copy the remaining data to the SMB packet + + byte[] byts = clbuf.toString().getBytes(); + for (int i = 0; i < byts.length; i++) + buf[pos++] = byts[i]; + + int pwdLen = ascPwd != null ? ascPwd.length : 0; + pkt.setByteCount(pwdLen + byts.length); + } + + // Exchange an SMB session setup packet with the remote file server + + pkt.ExchangeSMB(this, pkt, true); + + // Save the session user id + + setUserId(pkt.getUserId()); + + // Check if the session was created as a guest + + if (pkt.getParameterCount() >= 3) + { + + // Set the guest status for the session + + setGuest(pkt.getParameter(2) != 0 ? true : false); + } + + // The response packet should also have the server OS, LAN Manager type + // and primary domain name. + + if (pkt.getByteCount() > 0) + { + + // Get the packet buffer and byte offset + + byte[] buf = pkt.getBuffer(); + int offset = pkt.getByteOffset(); + int maxlen = offset + pkt.getByteCount(); + + // Get the server OS + + String srvOS = DataPacker.getString(buf, offset, maxlen); + setOperatingSystem(srvOS); + + offset += srvOS.length() + 1; + maxlen -= srvOS.length() + 1; + + // Get the LAN Manager type + + String lanman = DataPacker.getString(buf, offset, maxlen); + setLANManagerType(lanman); + + // Check if we have the primary domain for this session + + if (getDomain() == null || getDomain().length() == 0) + { + + // Get the domain name string + + offset += lanman.length() + 1; + maxlen += lanman.length() + 1; + + String dom = DataPacker.getString(buf, offset, maxlen); + setDomain(dom); + } + } + + // Check for a core protocol session, set the maximum packet size + + if (getDialect() == Dialect.Core || getDialect() == Dialect.CorePlus) + { + + // Set the maximum packet size to be used on this session + + setMaximumPacketSize(pkt.getParameter(2)); + } + } + + /** + * Process the negotiate response SMB packet + * + * @exception IOException If a network error occurs + * @eception SMBException If a CIFS error occurs + */ + private void processNegotiateResponse() throws IOException, SMBException + { + + // Set the security mode flags + + int keyLen = 0; + boolean unicodeStr = false; + int encAlgorithm = PasswordEncryptor.LANMAN; + int defFlags2 = 0; + + if (getDialect() == Dialect.NT) + { + + // Read the returned negotiate parameters, for NT dialect the parameters are not aligned + + m_pkt.resetParameterPointer(); + m_pkt.skipBytes(2); // skip the dialect index + + setSecurityMode(m_pkt.unpackByte()); + + // Set the maximum virtual circuits and multiplxed requests allowed by the server + + setMaximumMultiplexedRequests(m_pkt.unpackWord()); + setMaximumVirtualCircuits(m_pkt.unpackWord()); + + // Set the maximum buffer size + + setMaximumPacketSize(m_pkt.unpackInt()); + + // Skip the maximum raw buffer size and session key + + m_pkt.skipBytes(8); + + // Set the server capabailities + + setCapabilities(m_pkt.unpackInt()); + + // Check if extended security is enabled + + if ( supportsExtendedSecurity()) + m_extendedSec = true; + + // Get the server system time and timezone + + SMBDate srvTime = NTTime.toSMBDate(m_pkt.unpackLong()); + int tzone = m_pkt.unpackWord(); + + // Get the encryption key length + + keyLen = m_pkt.unpackByte(); + + // Indicate that strings are UniCode + + unicodeStr = true; + + // Use NTLMv1 password encryption + + encAlgorithm = PasswordEncryptor.NTLM1; + + // Set the default flags for subsequent SMB requests + + defFlags2 = SMBPacket.FLG2_LONGFILENAMES + SMBPacket.FLG2_UNICODE + SMBPacket.FLG2_LONGERRORCODE; + + if ( isUsingExtendedSecurity()) + defFlags2 += SMBPacket.FLG2_EXTENDEDSECURITY; + } + else if (getDialect() > Dialect.CorePlus) + { + + // Set the security mode and encrypted password mode + + int secMode = m_pkt.getParameter(1); + setSecurityMode((secMode & 0x01) != 0 ? SecurityModeUser : SecurityModeShare); + + if (m_pkt.getParameterCount() >= 11) + keyLen = m_pkt.getParameter(11) & 0xFF; // should always be 8 + + // Set the maximum virtual circuits and multiplxed requests allowed by the server + + setMaximumMultiplexedRequests(m_pkt.getParameter(3)); + setMaximumVirtualCircuits(m_pkt.getParameter(4)); + + // Check if Unicode strings are being used + + if (m_pkt.isUnicode()) + unicodeStr = true; + + // Set the default flags for subsequent SMB requests + + defFlags2 = SMBPacket.FLG2_LONGFILENAMES; + } + + // Set the default packet flags for this session + + setDefaultFlags2(defFlags2); + + // Get the server details from the negotiate SMB packet + + if (m_pkt.getByteCount() > 0) + { + + // Get the returned byte area length and offset + + int bytsiz = m_pkt.getByteCount(); + int bytpos = m_pkt.getByteOffset(); + byte[] buf = m_pkt.getBuffer(); + + // Original format response + + if ( isUsingExtendedSecurity() == false) + { + // Extract the challenge response key, if specified + + if (keyLen > 0) + { + + // Allocate a buffer for the challenge response key + + byte[] encryptKey = new byte[keyLen]; + + // Copy the challenge response key + + for (int keyIdx = 0; keyIdx < keyLen; keyIdx++) + encryptKey[keyIdx] = buf[bytpos++]; + + // Set the sessions encryption key + + setEncryptionKey(encryptKey); + } + + // Extract the domain name + + String dom; + + if (unicodeStr == false) + dom = DataPacker.getString(buf, bytpos, bytsiz); + else + dom = DataPacker.getUnicodeString(buf, bytpos, bytsiz / 2); + setDomain(dom); + } + else + { + // Extract the server GUID + + m_serverGUID = new byte[16]; + System.arraycopy(buf, bytpos, m_serverGUID, 0, 16); + + // Run the first phase of the extended security session setup to get the challenge + // from the server + + doExtendedSessionSetupPhase1(); + } + } + } + + /** + * Send the first stage of the extended security session setup + * + * @exception IOException If a network error occurs + * @eception SMBException If a CIFS error occurs + */ + private final void doExtendedSessionSetupPhase1() throws IOException, SMBException + { + // Create a session setup packet + + SMBPacket pkt = new SMBPacket(); + + pkt.setCommand(PacketType.SessionSetupAndX); + + pkt.setFlags(getDefaultFlags()); + pkt.setFlags2(getDefaultFlags2()); + + // Build the extended session setup phase 1 request + + pkt.setParameterCount(12); + pkt.setAndXCommand(0xFF); // no secondary command + pkt.setParameter(1, 0); // offset to next command + pkt.setParameter(2, DefaultPacketSize); + pkt.setParameter(3, 1); + pkt.setParameter(4, 0); // virtual circuit number + pkt.setParameterLong(5, 0); // session key + + // Clear the security blob length and reserved area + + pkt.setParameter(7, 0); // security blob length + pkt.setParameterLong(8, 0); // reserved + + // Send the client capabilities + + int caps = Capability.LargeFiles + Capability.Unicode + Capability.NTSMBs + Capability.NTStatus + + Capability.RemoteAPIs + Capability.ExtendedSecurity; + + // Set the client capabilities + + pkt.setParameterLong(10, caps); + + // Get the offset to the session setup request byte data + + int pos = pkt.getByteOffset(); + pkt.setPosition(pos); + + // Create a type 1 NTLM message using the session setup request buffer + + Type1NTLMMessage type1Msg = new Type1NTLMMessage(pkt.getBuffer(), pos, 0); + + int type1Flags = getPCShare().getExtendedSecurityFlags(); + if ( type1Flags == 0) + type1Flags = NTLM.FlagNegotiateUnicode + NTLM.FlagNegotiateNTLM + NTLM.FlagRequestTarget; + + type1Msg.buildType1(type1Flags, null, null); + + // Update the request buffer position + + pkt.setPosition(pos + type1Msg.getLength()); + + // Set the security blob length + + pkt.setParameter(7, type1Msg.getLength()); + + // Pack the OS details + + pkt.packString("Java VM", true); + pkt.packString("JLAN", true); + + pkt.packString("", true); + + // Set the packet length + + pkt.setByteCount(pkt.getPosition() - pos); + + // Exchange an SMB session setup packet with the remote file server + + pkt.ExchangeSMB(this, pkt, false); + + // Check the error status, should be a warning status to indicate more processing required + + if ( pkt.isLongErrorCode() == false || pkt.getLongErrorCode() != SMBStatus.NTMoreProcessingRequired) + pkt.checkForError(); + + // Save the session user id + + setUserId(pkt.getUserId()); + + // The response packet should also have the type 2 security blob + + int type2Len = pkt.getParameter(3); + if (pkt.getByteCount() > 0) + { + + // Get the packet buffer and byte offset + + byte[] buf = pkt.getBuffer(); + int offset = pkt.getByteOffset(); + int maxlen = offset + pkt.getByteCount(); + + // Take a copy of the type 2 security blob + + m_type2Msg = new Type2NTLMMessage(); + m_type2Msg.copyFrom(buf, offset, type2Len); + + // Get the encryption key from the security blob + + m_encryptKey = m_type2Msg.getChallenge(); + + // Update the byte area offset and align + + offset = DataPacker.wordAlign(offset + type2Len); + maxlen -= type2Len; + + // Get the server OS + + String srvOS = DataPacker.getString(buf, offset, maxlen); + setOperatingSystem(srvOS); + + offset += srvOS.length() + 1; + maxlen -= srvOS.length() + 1; + + // Get the LAN Manager type + + String lanman = DataPacker.getString(buf, offset, maxlen); + setLANManagerType(lanman); + } + } + + /** + * Send the second stage of the extended security session setup + * + * @param domain String + * @param userName String + * @param wksName String + * @param lmPwd byte[] + * @param ntlmPwd byte[] + * @exception IOException If a network error occurs + * @eception SMBException If a CIFS error occurs + */ + private final void doExtendedSessionSetupPhase2(String domain, String userName, String wksName, + byte[] lmPwd, byte[] ntlmPwd) throws IOException, SMBException + { + // Check if the domain name has been specified, if not then use the domain name from the + // original connection details or the servers domain name + + if ( domain == null) + { + if ( getPCShare().hasDomain() && getPCShare().getDomain().length() > 0) + domain = getPCShare().getDomain(); + else + domain = m_type2Msg.getTarget(); + } + + // Create a session setup packet + + SMBPacket pkt = new SMBPacket(); + + pkt.setCommand(PacketType.SessionSetupAndX); + + pkt.setFlags(getDefaultFlags()); + pkt.setFlags2(getDefaultFlags2()); + + pkt.setUserId(getUserId()); + + // Build the extended session setup phase 2 request + + pkt.setParameterCount(12); + pkt.setAndXCommand(0xFF); // no secondary command + pkt.setParameter(1, 0); // offset to next command + pkt.setParameter(2, DefaultPacketSize); + pkt.setParameter(3, 1); + pkt.setParameter(4, 0); // virtual circuit number + pkt.setParameterLong(5, 0); // session key + + // Clear the security blob length and reserved area + + pkt.setParameter(7, 0); // security blob length + pkt.setParameterLong(8, 0); // reserved + + // Send the client capabilities + + int caps = Capability.LargeFiles + Capability.Unicode + Capability.NTSMBs + Capability.NTStatus + + Capability.RemoteAPIs + Capability.ExtendedSecurity; + + // Set the client capabilities + + pkt.setParameterLong(10, caps); + + // Get the offset to the session setup request byte data + + int pos = pkt.getByteOffset(); + pkt.setPosition(pos); + + // Create a type 3 NTLM message using the session setup request buffer + + Type3NTLMMessage type3Msg = new Type3NTLMMessage(pkt.getBuffer(), pos, 0, true); + + type3Msg.buildType3(lmPwd, ntlmPwd, domain, userName, wksName != null ? wksName : "", null, m_type2Msg.getFlags()); + + // Update the request buffer position + + pkt.setPosition(pos + type3Msg.getLength()); + + // Set the security blob length + + pkt.setParameter(7, type3Msg.getLength()); + + // Pack the OS details + + pkt.packString("Java VM", true); + pkt.packString("JLAN", true); + + pkt.packString("", true); + + // Set the packet length + + pkt.setByteCount(pkt.getPosition() - pos); + + // Exchange an SMB session setup packet with the remote file server + + pkt.ExchangeSMB(this, pkt, true); + + // Set the guest status for the session + + setGuest(pkt.getParameter(2) != 0 ? true : false); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java new file mode 100644 index 0000000000..159effcc27 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruAuthenticator.java @@ -0,0 +1,693 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.passthru; + +import java.util.Hashtable; + +import javax.transaction.UserTransaction; + +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.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.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; +import org.apache.commons.logging.LogFactory; + +/** + * Passthru Authenticator Class + *

+ * Authenticate users accessing the CIFS server by validating the user against a domain controller + * or other server on the network. + * + * @author GKSpencer + */ +public class PassthruAuthenticator extends SrvAuthenticator implements SessionListener +{ + // Debug logging + + private static final Log logger = LogFactory.getLog("org.alfresco.smb.protocol.auth"); + + // Constants + + public final static int DefaultSessionTmo = 5000; // 5 seconds + public final static int MinSessionTmo = 2000; // 2 seconds + public final static int MaxSessionTmo = 30000; // 30 seconds + + // Passthru servers used to authenticate users + + private PassthruServers m_passthruServers; + + // SMB server + + private SMBServer m_server; + + // Sessions that are currently in the negotiate/session setup state + + private Hashtable m_sessions; + + /** + * Passthru Authenticator Constructor + *

+ * Default to user mode security with encrypted password support. + */ + public PassthruAuthenticator() + { + setAccessMode(SrvAuthenticator.USER_MODE); + setEncryptedPasswords(true); + + // Allocate the session table + + m_sessions = new Hashtable(); + } + + /** + * 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) + { + // 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) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Null CIFS logon allowed"); + + return SrvAuthenticator.AUTH_ALLOW; + } + + // Check if the client is already authenticated, and it is not a null logon + + if ( client.getAuthenticationToken() != null && client.getLogonType() != ClientInfo.LogonNull) + { + // Use the existing authentication token + + m_authComponent.setCurrentUser(client.getUserName()); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Re-using existing authentication token"); + + // Return the authentication status + + return client.getLogonType() != ClientInfo.LogonGuest ? AUTH_ALLOW : AUTH_GUEST; + } + + // Check if this is a guest logon + + int authSts = AUTH_DISALLOW; + + if ( client.isGuest() || client.getUserName().equalsIgnoreCase(getGuestUserName())) + { + // Check if guest logons are allowed + + if ( allowGuest() == false) + return AUTH_DISALLOW; + + // Get a guest authentication token + + doGuestLogon( client, sess); + + // Indicate logged on as guest + + authSts = AUTH_GUEST; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Authenticated user " + client.getUserName() + " sts=" + getStatusAsString(authSts)); + + // Return the guest status + + return authSts; + } + + // Find the active authentication session details for the server session + + PassthruDetails passDetails = m_sessions.get(sess.getUniqueId()); + + if (passDetails != null) + { + + try + { + + // Authenticate the user by passing the hashed password to the authentication server + // using the session that has already been setup. + + AuthenticateSession authSess = passDetails.getAuthenticateSession(); + authSess.doSessionSetup(client.getUserName(), client.getANSIPassword(), client.getPassword()); + + // Check if the user has been logged on as a guest + + if (authSess.isGuest()) + { + + // Check if the local server allows guest access + + if (allowGuest() == true) + { + // Get a guest authentication token + + doGuestLogon( client, sess); + + // Allow the user access as a guest + + authSts = SrvAuthenticator.AUTH_GUEST; + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("Passthru authenticate user=" + client.getUserName() + ", GUEST"); + } + } + else + { + // Wrap the service calls in a transaction + + UserTransaction tx = m_transactionService.getUserTransaction( true); + + try + { + // Start the transaction + + tx.begin(); + + // Map the passthru username to an Alfresco person + + String username = client.getUserName(); + NodeRef userNode = m_personService.getPerson( username); + + if ( userNode != null) + { + // Get the person name and use that as the current user to line up with permission checks + + String personName = (String) m_nodeService.getProperty(userNode, ContentModel.PROP_USERNAME); + m_authComponent.setCurrentUser(personName); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Setting current user using person " + personName + " (username " + username + ")"); + } + else + { + // Set using the user name, lowercase the name if the person service is case insensitive + + if ( m_personService.getUserNamesAreCaseSensitive() == false) + username = username.toLowerCase(); + m_authComponent.setCurrentUser( username); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Setting current user using username " + username); + } + + // Allow the user full access to the server + + authSts = SrvAuthenticator.AUTH_ALLOW; + + // Debug + + if (logger.isDebugEnabled()) + logger.debug("Passthru authenticate user=" + client.getUserName() + ", FULL"); + } + finally + { + // Commit the transaction + + if ( tx != null) + { + try { + tx.commit(); + } + catch (Exception ex) + { + // Sink it + } + } + } + } + } + catch (Exception ex) + { + + // Debug + + logger.error(ex.getMessage()); + } + + // Keep the authentication session if the user session is an SMB session, else close the + // session now + + if ((sess instanceof SMBSrvSession) == false) + { + + // Remove the passthru session from the active list + + m_sessions.remove(sess.getUniqueId()); + + // Close the passthru authentication session + + try + { + + // Close the authentication session + + AuthenticateSession authSess = passDetails.getAuthenticateSession(); + authSess.CloseSession(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Closed auth session, sessId=" + authSess.getSessionId()); + } + catch (Exception ex) + { + + // Debug + + logger.error("Passthru error closing session (auth user)", ex); + } + } + } + else + { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug(" No PassthruDetails for " + sess.getUniqueId()); + } + + // 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) + { + + // Check for an SMB session + + byte[] chKey = 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) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Re-using existing challenge, already authenticated"); + + // 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); + } + } + + try + { + + // Open a connection to the authentication server + + AuthenticateSession authSess = m_passthruServers.openSession(); + if (authSess != null) + { + + // Create an entry in the active sessions table for the new session + + PassthruDetails passDetails = new PassthruDetails(sess, authSess); + m_sessions.put(sess.getUniqueId(), passDetails); + + // Use the challenge key returned from the authentication server + + chKey = authSess.getEncryptionKey(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Passthru sessId=" + authSess.getSessionId() + ", negotiate key=[" + + HexDump.hexString(chKey) + "]"); + } + } + catch (Exception ex) + { + + // Debug + + logger.error("Passthru error getting challenge", ex); + } + + // Return the challenge key + + return chKey; + } + + /** + * 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); + + // Create the passthru authentication server list + + m_passthruServers = new PassthruServers(); + + // Check if the session timeout has been specified + + ConfigElement sessTmoElem = params.getChild("Timeout"); + if (sessTmoElem != null) + { + + try + { + + // Validate the session timeout value + + int sessTmo = Integer.parseInt(sessTmoElem.getValue()); + + // Range check the timeout + + if ( sessTmo < MinSessionTmo || sessTmo > MaxSessionTmo) + throw new InvalidConfigurationException("Invalid session timeout, valid range is " + + MinSessionTmo + " to " + MaxSessionTmo); + + // Set the session timeout for connecting to an authentication server + + m_passthruServers.setConnectionTimeout( sessTmo); + } + catch (NumberFormatException ex) + { + throw new InvalidConfigurationException("Invalid timeout value specified"); + } + } + + // Check if the local server should be used + + String srvList = null; + + if (params.getChild("LocalServer") != null) + { + + // Get the local server name, trim the domain name + + srvList = config.getLocalServerName(true); + if(srvList == null) + throw new AlfrescoRuntimeException("Passthru authenticator failed to get local server name"); + } + + // Check if a server name has been specified + + ConfigElement srvNamesElem = params.getChild("Server"); + + if (srvNamesElem != null && srvNamesElem.getValue().length() > 0) + { + + // Check if the server name was already set + + if (srvList != null) + throw new AlfrescoRuntimeException("Set passthru server via local server or specify name"); + + // Get the passthru authenticator server name + + srvList = srvNamesElem.getValue(); + } + + // If the passthru server name has been set initialize the passthru connection + + if (srvList != null) + { + // Initialize using a list of server names/addresses + + m_passthruServers.setServerList(srvList); + } + else + { + + // Get the domain/workgroup name + + String domainName = null; + + // Check if the local domain/workgroup should be used + + if (params.getChild("LocalDomain") != null) + { + // Get the local domain/workgroup name + + domainName = config.getLocalDomainName(); + } + + // Check if a domain name has been specified + + ConfigElement domNameElem = params.getChild("Domain"); + + if (domNameElem != null && domNameElem.getValue().length() > 0) + { + + // Check if the authentication server has already been set, ie. server name was also specified + + if (srvList != null) + throw new AlfrescoRuntimeException("Specify server or domain name for passthru authentication"); + + domainName = domNameElem.getValue(); + } + + // If the domain name has been set initialize the passthru connection + + if (domainName != null) + { + // Initialize using the domain + + m_passthruServers.setDomain(domainName); + } + } + + // Check if we have an authentication server + + if (m_passthruServers.getTotalServerCount() == 0) + throw new AlfrescoRuntimeException("No valid authentication servers found for passthru"); + } + + /** + * Close the authenticator, perform cleanup + */ + public void closeAuthenticator() + { + // Close the passthru authentication server list + + if ( m_passthruServers != null) + m_passthruServers.shutdown(); + } + + /** + * SMB server session closed notification + * + * @param sess SrvSession + */ + public void sessionClosed(SrvSession sess) + { + + // Check if there is an active session to the authentication server for this local + // session + + PassthruDetails passDetails = m_sessions.get(sess.getUniqueId()); + + if (passDetails != null) + { + + // Remove the passthru session from the active list + + m_sessions.remove(sess.getUniqueId()); + + // Close the passthru authentication session + + try + { + + // Close the authentication session + + AuthenticateSession authSess = passDetails.getAuthenticateSession(); + authSess.CloseSession(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Closed auth session, sessId=" + authSess.getSessionId()); + } + catch (Exception ex) + { + + // Debug + + logger.error("Passthru error closing session (closed)", ex); + } + } + } + + /** + * SMB server session created notification + * + * @param sess SrvSession + */ + public void sessionCreated(SrvSession sess) + { + } + + /** + * User successfully logged on notification + * + * @param sess SrvSession + */ + public void sessionLoggedOn(SrvSession sess) + { + + // Check if the client information has an empty user name, if so then do not close the + // authentication + // session + + if (sess.hasClientInformation() && sess.getClientInformation().getUserName() != null + && sess.getClientInformation().getUserName().length() > 0) + { + + // Check if there is an active session to the authentication server for this local + // session + + PassthruDetails passDetails = m_sessions.get(sess.getUniqueId()); + + if (passDetails != null) + { + + // Remove the passthru session from the active list + + m_sessions.remove(sess.getUniqueId()); + + // Close the passthru authentication session + + try + { + + // Close the authentication session + + AuthenticateSession authSess = passDetails.getAuthenticateSession(); + authSess.CloseSession(); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Closed auth session, sessId=" + authSess.getSessionId()); + } + catch (Exception ex) + { + + // Debug + + logger.error("Passthru error closing session (logon)", ex); + } + } + } + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruDetails.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruDetails.java new file mode 100644 index 0000000000..069af59c31 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruDetails.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.passthru; + +import org.alfresco.filesys.server.SrvSession; + +/** + * Passthru Details Class + *

+ * Contains the details of a passthru connection to a remote server and the local session that the + * request originated from. + */ +class PassthruDetails +{ + + // Server session + + private SrvSession m_sess; + + // Authentication session connected to the remote server + + private AuthenticateSession m_authSess; + + /** + * Class constructor + * + * @param sess SrvSession + * @param authSess AuthenticateSession + */ + public PassthruDetails(SrvSession sess, AuthenticateSession authSess) + { + m_sess = sess; + m_authSess = authSess; + } + + /** + * Return the session details + * + * @return SrvSession + */ + public final SrvSession getSession() + { + return m_sess; + } + + /** + * Return the authentication session that is connected to the remote server + * + * @return AuthenticateSession + */ + public final AuthenticateSession getAuthenticateSession() + { + return m_authSess; + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServerDetails.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServerDetails.java new file mode 100644 index 0000000000..c1112a1fda --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServerDetails.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.passthru; + +import java.net.*; +import java.util.*; + +/** + *

Contains the details of a server used for passthru authentication, the current status of the server + * and count of authentications done via this server. + * + * @author GKSpencer + */ +public class PassthruServerDetails +{ + // Server details + + private String m_name; + private String m_domain; + private InetAddress m_address; + + // Server status + + private boolean m_online; + + // Authentication statistics + + private int m_authCount; + private long m_lastAuthTime; + + /** + * Class constructor + * + * @param name String + * @param domain String + * @param addr InetAddress + * @param online boolean + */ + PassthruServerDetails(String name, String domain, InetAddress addr, boolean online) + { + m_name = name; + m_domain = domain; + m_address = addr; + m_online = online; + } + + /** + * Return the server name + * + * @return String + */ + public final String getName() + { + return m_name; + } + + /** + * Return the domain name + * + * @return String + */ + public final String getDomain() + { + return m_domain; + } + + /** + * Return the server address + * + * @return InetAddress + */ + public final InetAddress getAddress() + { + return m_address; + } + + /** + * Return the online status of the server + * + * @return boolean + */ + public final boolean isOnline() + { + return m_online; + } + + /** + * Return the authentication count for the server + * + * @return int + */ + public final int getAuthenticationCount() + { + return m_authCount; + } + + /** + * Return the date/time of the last authentication by this server + * + * @return long + */ + public final long getAuthenticationDateTime() + { + return m_lastAuthTime; + } + + /** + * Set the online status for the server + * + * @param online boolean + */ + public final void setOnline(boolean online) + { + m_online = online; + } + + /** + * Update the authentication count and date/time + */ + public synchronized final void incrementAuthenticationCount() + { + m_authCount++; + m_lastAuthTime = System.currentTimeMillis(); + } + + /** + * Return the hash code for this object + * + * @return int + */ + public int hashCode() + { + return m_address.hashCode(); + } + + /** + * Return the passthru server details as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + if ( getDomain() != null) + { + str.append(getDomain()); + str.append("\\"); + } + str.append(getName()); + + str.append(":"); + str.append(getAddress().getHostAddress()); + + str.append(isOnline() ? ":Online" : ":Offline"); + + str.append(":"); + str.append(getAuthenticationCount()); + str.append(","); + str.append(getAuthenticationDateTime() != 0L ? new Date(getAuthenticationDateTime()) : "0"); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java new file mode 100644 index 0000000000..97aa5d1fb6 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/PassthruServers.java @@ -0,0 +1,727 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.passthru; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.*; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.netbios.NetBIOSName; +import org.alfresco.filesys.netbios.NetBIOSNameList; +import org.alfresco.filesys.netbios.NetBIOSSession; +import org.alfresco.filesys.smb.PCShare; +import org.alfresco.filesys.util.IPAddress; +import org.apache.log4j.*; + +/** + * Passthru Servers Class + * + *

Contains a list of one or more servers that are used for passthru authentication. The status of the + * servers is tracked so that offline servers are not used but periodically monitored so that they can be + * returned to the online list of servers. + * + *

The server list may be initialized from a list of server names or addresses, or by specifying a domain + * name in which case the primary and backup domain controllers will be used. + * + * @author GKSpencer + * + */ +public class PassthruServers +{ + // Debug logging + + private static final Logger logger = Logger.getLogger("org.alfresco.smb.protocol.auth"); + + // Default timeout for setting up a new session + + private static final int DefaultConnectTimeout = 5000; // 5 seconds + + // Default interval to check if offline servers + + private static final long DefaultOfflineCheckInterval = 5 * 60000; // 5 minutes + + // List of online and offline authentication servers + + private List m_onlineList; + private List m_offlineList; + + // Timeout value when opening a session to an authentication server, in milliseconds + + private int m_tmo = DefaultConnectTimeout; + + // Domain name, if using domain controllers + + private String m_domain; + + // Offline server check interval + + private long m_offlineCheckInterval = DefaultOfflineCheckInterval; + + // Offline server checker thread + + PassthruOfflineChecker m_offlineChecker; + + /** + * Inner class used to periodically check offline servers to see if they are back online + */ + class PassthruOfflineChecker extends Thread + { + // Thread shutdown request flag + + private boolean m_ishutdown; + + /** + * Default constructor + */ + PassthruOfflineChecker() + { + setDaemon(true); + setName("PassthruOfflineChecker"); + start(); + } + + /** + * Main thread code + */ + public void run() + { + // Loop until shutdown + + m_ishutdown = false; + + while ( m_ishutdown == false) + { + // Sleep for a while + + try + { + sleep( m_offlineCheckInterval); + } + catch ( InterruptedException ex) + { + } + + // Check if shutdown has been requested + + if( m_ishutdown == true) + continue; + + // Check if there are any offline servers to check + + if ( getOfflineServerCount() > 0) + { + // Enumerate the offline server list + + int idx = 0; + PassthruServerDetails offlineServer = null; + PCShare authShare = new PCShare("", "IPC$", "", ""); + AuthenticateSession authSess = null; + + while ( idx < getOfflineServerCount()) + { + // Get an offline server from the list + + offlineServer = m_offlineList.get(idx); + + if ( offlineServer != null) + { + try + { + // Set the target host name + + authShare.setNodeName(offlineServer.getAddress().getHostAddress()); + + // Try and connect to the authentication server + + authSess = AuthSessionFactory.OpenAuthenticateSession( authShare, getConnectionTimeout()); + + // Close the session + + try + { + authSess.CloseSession(); + } + catch ( Exception ex) + { + } + + // Authentication server is online, move it to the online list + + serverOnline(offlineServer); + } + catch ( Exception ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Passthru offline check failed for " + offlineServer.getName()); + } + + // Check if the server is now online + + if ( offlineServer.isOnline() == false) + idx++; + } + } + } + } + + // Debug + + if( logger.isDebugEnabled()) + logger.debug("Passthru offline checker thread closed"); + } + + /** + * Shutdown the checker thread + */ + public final void shutdownRequest() + { + m_ishutdown = true; + this.interrupt(); + } + + /** + * Wakeup the offline checker thread to process the offline server list + */ + public final void processOfflineServers() + { + this.interrupt(); + } + } + + /** + * Default constructor + */ + public PassthruServers() + { + // Create the server lists + + m_onlineList = new ArrayList(); + m_offlineList = new ArrayList(); + + // Create and start the offline server checker thread + + m_offlineChecker = new PassthruOfflineChecker(); + } + + /** + * Return the count of online authentication servers + * + * @return int + */ + public final int getOnlineServerCount() + { + return m_onlineList.size(); + } + + /** + * Return the count of offline authentication servers + * + * @return int + */ + public final int getOfflineServerCount() + { + return m_offlineList.size(); + } + + /** + * Return the total count of online and offline servers + * + * @return int + */ + public final int getTotalServerCount() + { + return m_onlineList.size() + m_offlineList.size(); + } + + /** + * Determine if there are online servers + * + * @return boolean + */ + public final boolean hasOnlineServers() + { + return m_onlineList.size() > 0 ? true : false; + } + + /** + * Return the connection timeout, in milliseconds + * + * @return int + */ + public final int getConnectionTimeout() + { + return m_tmo; + } + + /** + * Determine if the authentication servers are domain controllers + * + * @return boolean + */ + public final boolean isDomainAuthentication() + { + return m_domain != null ? true : false; + } + + /** + * Return the domain name + * + * @return String + */ + public final String getDomain() + { + return m_domain; + } + + /** + * Open a new session to an authentication server + * + * @return AuthenticateSession + */ + public final AuthenticateSession openSession() + { + return openSession( 0); + } + + /** + * Open a new session to an authentication server + * + * @param extFlags int + * @return AuthenticateSession + */ + public final AuthenticateSession openSession(int extFlags) + { + // Get the details of an authentication server to connect to + + PassthruServerDetails passthruServer = getAuthenticationServer(); + if ( passthruServer == null) + return null; + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Open authenticate session to " + passthruServer); + + // Open a new authentication session to the server + + PCShare authShare = new PCShare(passthruServer.getAddress().getHostAddress(), "IPC$", "", ""); + authShare.setExtendedSecurityFlags( extFlags); + + AuthenticateSession authSess = null; + + while ( authSess == null && passthruServer != null && hasOnlineServers()) { + + try + { + // Open a session to the current authentication server + + authSess = AuthSessionFactory.OpenAuthenticateSession( authShare, getConnectionTimeout()); + + // Update the passthru statistics + + passthruServer.incrementAuthenticationCount(); + } + catch ( Exception ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Failed to connect to " + passthruServer + " : " + ex.getMessage()); + + // Failed to connect to the current authentication server, mark the server as offline + + serverOffline(passthruServer); + } + + // Check if we have a valid session + + if ( authSess == null) + { + // Try another authentication server + + passthruServer = getAuthenticationServer(); + + // Debug + + if(logger.isDebugEnabled()) + logger.debug("Trying authentication server " + passthruServer); + } + } + + // Return the authentication session + + return authSess; + } + + /** + * Return the details of an online server to use for authentication + * + * @return PassthruServerDetails + */ + protected PassthruServerDetails getAuthenticationServer() + { + // Rotate the head of the list and return the new head of list server details + + PassthruServerDetails passthruServer = null; + + synchronized ( m_onlineList) + { + if ( m_onlineList.size() > 1) + m_onlineList.add(m_onlineList.remove(0)); + if ( m_onlineList.size() > 0) + passthruServer = m_onlineList.get(0); + } + + return passthruServer; + } + + /** + * Move a server from the list of online servers to the offline list + * + * @param server PassthruServerDetails + */ + protected final void serverOffline(PassthruServerDetails server) + { + // Set the server status + + server.setOnline(false); + + // Remove the server from the online list + + synchronized( m_onlineList) + { + m_onlineList.remove(server); + } + + // Add it to the offline list + + synchronized( m_offlineList) + { + m_offlineList.add( server); + } + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Passthru server offline, " + server); + } + + /** + * Move a server from the list of offline servers to the online list + * + * @param server PassthruServerDetails + */ + protected final void serverOnline(PassthruServerDetails server) + { + // Set the server status + + server.setOnline(true); + + // Remove the server from the offline list + + synchronized( m_offlineList) + { + m_offlineList.remove(server); + } + + // Add it to the online list + + synchronized( m_onlineList) + { + m_onlineList.add( server); + } + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Passthru server online, " + server); + } + + /** + * Set the session connect timeout value, in milliseconds + * + * @param tmo int + */ + public final void setConnectionTimeout(int tmo) + { + m_tmo = tmo; + } + + /** + * Set the offline check interval, in seconds + * + * @param interval long + */ + public final void setOfflineCheckInterval(long interval) + { + m_offlineCheckInterval = interval * 1000L; + } + + /** + * Set the list of servers to use for passthru authentication using a comma delimeted list + * of server names/addresses + * + * @param servers String + */ + public final void setServerList(String servers) + { + // Split the server list into seperate name/address tokens + + StringTokenizer tokens = new StringTokenizer(servers, ","); + + while ( tokens.hasMoreTokens()) + { + // Get the current server name/address + + String srvName = tokens.nextToken().trim(); + + // If a name a has been specified convert it to an address, if an address has been specified + // then convert to a name. + + InetAddress srvAddr = null; + + if ( IPAddress.isNumericAddress(srvName)) + { + // Get the server name + + try + { + // Get the host address and name + + srvAddr = InetAddress.getByName(srvName); + srvName = srvAddr.getHostName(); + } + catch ( UnknownHostException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Passthru failed to find name/address for " + srvName); + } + } + else + { + // Get the server address + + try + { + srvAddr = InetAddress.getByName(srvName); + } + catch ( UnknownHostException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Passthru failed to find address for " + srvName); + } + } + + // Create the passthru server details and add to the list of offline servers + + if ( srvName != null && srvAddr != null) + { + // Create the passthru server details + + PassthruServerDetails passthruServer = new PassthruServerDetails(srvName, null, srvAddr, false); + m_offlineList.add( passthruServer); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Added passthru server " + passthruServer); + } + } + + // Wakeup the server checker thread to check each of the servers just added and move servers that are + // accessible to the online list + + m_offlineChecker.processOfflineServers(); + } + + /** + * Set the domain to use for passthru authentication + * + * @param domain String + */ + public final void setDomain(String domain) + { + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug("Passthru finding domain controller for " + domain + " ..."); + + // Find a domain controller or the browse master + + NetBIOSName nbName = null; + + try + { + // Find a domain controller + + nbName = NetBIOSSession.FindName(domain, NetBIOSName.DomainControllers, getConnectionTimeout()); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug(" Found " + nbName.numberOfAddresses() + " domain controller(s)"); + } + catch (IOException ex) + { + } + + // If we did not find a domain controller look for the browse master + + if ( nbName == null) { + + try + { + // Try and find the browse master for the workgroup + + nbName = NetBIOSSession.FindName( domain, NetBIOSName.MasterBrowser, getConnectionTimeout()); + + // DEBUG + + if (logger.isDebugEnabled()) + logger.debug(" Found browse master at " + nbName.getIPAddressString(0)); + } + catch (IOException ex) + { + throw new AlfrescoRuntimeException("Failed to find domain controller or browse master for " + domain); + } + } + + // Add the passthru server(s) + // + // Try and convert the address to a name for each domain controller + + for ( int i = 0; i < nbName.numberOfAddresses(); i++) + { + // Get the domain controller name + + InetAddress dcAddr = null; + String dcName = null; + + try + { + // Get the current domain controller address + + dcAddr = InetAddress.getByName(nbName.getIPAddressString(i)); + + // Get the list of NetBIOS names for the domain controller + + NetBIOSNameList nameList = NetBIOSSession.FindNamesForAddress(dcAddr.getHostAddress()); + NetBIOSName dcNBName = nameList.findName(NetBIOSName.FileServer, false); + + if ( dcNBName != null) + dcName = dcNBName.getName(); + } + catch (UnknownHostException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Invalid address for server " + nbName.getIPAddressString(i)); + } + catch (Exception ex) + { + // Failed to get domain controller name, use the address + + dcName = dcAddr.getHostAddress(); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Failed to get NetBIOS name for server " + dcAddr); + } + + // Create a passthru server entry for the domain controller + + if ( dcAddr != null) + { + // Create the passthru authentication server record + + PassthruServerDetails passthruServer = new PassthruServerDetails(dcName, domain, dcAddr, false); + m_offlineList.add(passthruServer); + + // Debug + + if ( logger.isDebugEnabled()) + logger.debug("Added passthru server " + passthruServer); + } + } + + // Wakeup the server checker thread to check each of the servers just added and move servers that are + // accessible to the online list + + m_offlineChecker.processOfflineServers(); + } + + /** + * Shutdown passthru authentication + */ + public final void shutdown() + { + // Shutdown the offline server checker thread + + m_offlineChecker.shutdownRequest(); + + // Clear the online and offline server lists + + m_onlineList.clear(); + m_offlineList.clear(); + } + + /** + * Return the passthru server details as a string + * + * @return String + */ + public String toString() + { + StringBuilder str = new StringBuilder(); + + str.append("["); + + if ( isDomainAuthentication()) + { + str.append("Domain:"); + str.append(getDomain()); + } + else + str.append("Servers:"); + + str.append(",Online="); + str.append(getOnlineServerCount()); + str.append(",Offline="); + str.append(getOfflineServerCount()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/SMBPacket.java b/source/java/org/alfresco/filesys/server/auth/passthru/SMBPacket.java new file mode 100644 index 0000000000..d2ebf5b2a0 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/SMBPacket.java @@ -0,0 +1,1378 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.passthru; + +import java.io.IOException; + +import org.alfresco.filesys.netbios.NetBIOSSession; +import org.alfresco.filesys.netbios.RFCNetBIOSProtocol; +import org.alfresco.filesys.smb.NetworkSession; +import org.alfresco.filesys.smb.PacketType; +import org.alfresco.filesys.smb.SMBException; +import org.alfresco.filesys.smb.SMBStatus; +import org.alfresco.filesys.util.DataPacker; + +/** + * SMB packet type class + * + * @author GKSpencer + */ +public class SMBPacket +{ + + // SMB packet offsets, assuming an RFC NetBIOS transport + + public static final int SIGNATURE = RFCNetBIOSProtocol.HEADER_LEN; + public static final int COMMAND = 4 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int ERRORCODE = 5 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int ERRORCLASS = 5 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int ERROR = 7 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int FLAGS = 9 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int FLAGS2 = 10 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int PIDHIGH = 12 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int SID = 18 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int SEQNO = 20 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int TID = 24 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int PID = 26 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int UID = 28 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int MID = 30 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int WORDCNT = 32 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int ANDXCOMMAND = 33 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int ANDXRESERVED = 34 + RFCNetBIOSProtocol.HEADER_LEN; + public static final int PARAMWORDS = 33 + RFCNetBIOSProtocol.HEADER_LEN; + + // SMB packet header length for a transaction type request + + public static final int TRANS_HEADERLEN = 66 + RFCNetBIOSProtocol.HEADER_LEN; + + // Minimum receive length for a valid SMB packet + + public static final int MIN_RXLEN = 32; + + // Default buffer size to allocate for SMB packets + + public static final int DEFAULT_BUFSIZE = 4096; + + // Flag bits + + public static final int FLG_SUBDIALECT = 0x01; + public static final int FLG_CASELESS = 0x08; + public static final int FLG_CANONICAL = 0x10; + public static final int FLG_OPLOCK = 0x20; + public static final int FLG_NOTIFY = 0x40; + public static final int FLG_RESPONSE = 0x80; + + // Flag2 bits + + public static final int FLG2_LONGFILENAMES = 0x0001; + public static final int FLG2_EXTENDEDATTRIB = 0x0002; + public static final int FLG2_EXTENDEDSECURITY = 0x0800; + public static final int FLG2_READIFEXE = 0x2000; + public static final int FLG2_LONGERRORCODE = 0x4000; + public static final int FLG2_UNICODE = 0x8000; + + // Security mode bits + + public static final int SEC_USER = 0x0001; + public static final int SEC_ENCRYPT = 0x0002; + + // Raw mode bits + + public static final int RAW_READ = 0x0001; + public static final int RAW_WRITE = 0x0002; + + // SMB packet buffer + + private byte[] m_smbbuf; + + // Packet type + + private int m_pkttype; + + // Current byte area pack/unpack position + + protected int m_pos; + protected int m_endpos; + + // Time of last packet send + + protected long m_lastTxTime; + + /** + * Default constructor + */ + public SMBPacket() + { + m_smbbuf = new byte[DEFAULT_BUFSIZE]; + InitializeBuffer(); + } + + /** + * Construct an SMB packet using the specified packet buffer. + * + * @param buf SMB packet buffer. + */ + public SMBPacket(byte[] buf) + { + m_smbbuf = buf; + } + + /** + * Construct an SMB packet of the specified size. + * + * @param siz Size of SMB packet buffer to allocate. + */ + public SMBPacket(int siz) + { + m_smbbuf = new byte[siz]; + InitializeBuffer(); + } + + /** + * Check if a received SMB is valid, if not then throw an exception + * + * @exception SMBException + */ + public final void checkForError() throws SMBException + { + + // Check if a valid SMB response has been received + + if (isValidResponse() == false) + { + + // Check for NT error codes + + if (isLongErrorCode()) + throw new SMBException(SMBStatus.NTErr, getLongErrorCode()); + else + throw new SMBException(getErrorClass(), getErrorCode()); + } + } + + /** + * Clear the data byte count + */ + public final void clearBytes() + { + int offset = getByteOffset() - 2; + DataPacker.putIntelShort(0, m_smbbuf, offset); + } + + /** + * Check if the error class/code match the specified error/class + * + * @param errClass int + * @param errCode int + * @return boolean + */ + public final boolean equalsError(int errClass, int errCode) + { + if (getErrorClass() == errClass && getErrorCode() == errCode) + return true; + return false; + } + + /** + * Send the SMB packet and receive the response packet + * + * @param sess Network session to send/receive the packet over. + * @param rxPkt SMB packet to receive the response into. + * @param throwerr If true then throw an I/O error if an invalid response is received. + * @exception java.io.IOException If a network error occurs. + * @exception SMBException If an SMB level error occurs + */ + protected final synchronized void ExchangeLowLevelSMB(NetworkSession sess, SMBPacket rxPkt, boolean throwerr) + throws java.io.IOException, SMBException + { + + // Set multiplex id + + if (getMultiplexId() == 0) + setMultiplexId(1); + + // Send the SMB request + + sess.Send(m_smbbuf, getLength()); + + // Receive a response + + if (sess.Receive(rxPkt.getBuffer(), 0) >= MIN_RXLEN) + { + + // Check if the response is for the current request + + if (rxPkt.getCommand() == m_pkttype) + { + + // Check if a valid SMB response has been received + + if (throwerr == true) + checkForError(); + + // Valid packet received, return to caller + + return; + } + } + + // Invalid receive packet + + throw new java.io.IOException("Invalid SMB Receive Packet"); + } + + /** + * Send/receive an SMB protocol packet to the remote server. + * + * @param sess SMB session to send/receive data on. + * @param rxPkt SMB packet to receive the response into. + * @exception java.io.IOException If an I/O error occurs. + * @exception SMBException If an SMB level error occurs. + */ + public synchronized final void ExchangeSMB(AuthenticateSession sess, SMBPacket rxPkt) throws SMBException, + IOException + { + + // Call the main SMB exhchange method + + ExchangeSMB(sess, rxPkt, false); + } + + /** + * Send the SMB packet and receive the response packet + * + * @param sess SMB session to send/receive the packet over. + * @param rxPkt SMB packet to receive the response into. + * @param throwerr If true then throw an I/O error if an invalid response is received. + * @exception java.io.IOException If an I/O error occurs. + * @exception SMBException If an SMB level error occurs. + */ + public synchronized final void ExchangeSMB(AuthenticateSession sess, SMBPacket rxPkt, boolean throwerr) + throws SMBException, IOException + { + + // Set the process id, user id and multiplex id + + setProcessId(sess.getProcessId()); + setUserId(sess.getUserId()); + + if (getMultiplexId() == 0) + setMultiplexId(1); + + // Get the network session + + NetworkSession netSess = sess.getSession(); + + // Send the SMB request + + netSess.Send(m_smbbuf, getLength()); + + // Receive the response, other asynchronous responses may be received before the response + // for this request + + boolean rxValid = false; + + while (rxValid == false) + { + + // Receive a response + + if (netSess.Receive(rxPkt.getBuffer(), RFCNetBIOSProtocol.TMO) >= MIN_RXLEN) + { + + // Check if the response is for the current request + + if (rxPkt.getCommand() == m_pkttype) + { + + // Check if a valid SMB response has been received + + if (throwerr == true) + checkForError(); + + // Valid packet received, return to caller + + return; + } + } + } + + // Invalid receive packet + + throw new java.io.IOException("Invalid SMB Receive Packet"); + } + + /** + * Get the secondary command code + * + * @return Secondary command code + */ + public final int getAndXCommand() + { + return (int) (m_smbbuf[ANDXCOMMAND] & 0xFF); + } + + /** + * Return the byte array used for the SMB packet + * + * @return Byte array used for the SMB packet. + */ + public final byte[] getBuffer() + { + return m_smbbuf; + } + + /** + * Return the total buffer size available to the SMB request + * + * @return Total SMB buffer length available. + */ + public final int getBufferLength() + { + return m_smbbuf.length - RFCNetBIOSProtocol.HEADER_LEN; + } + + /** + * Return the available buffer space for data bytes + * + * @return int + */ + public final int getAvailableLength() + { + return m_smbbuf.length - DataPacker.longwordAlign(getByteOffset()); + } + + /** + * Get the data byte count for the SMB packet + * + * @return Data byte count + */ + public final int getByteCount() + { + + // Calculate the offset of the byte count + + int pos = PARAMWORDS + (2 * getParameterCount()); + return (int) DataPacker.getIntelShort(m_smbbuf, pos); + } + + /** + * Get the data byte area offset within the SMB packet + * + * @return Data byte offset within the SMB packet. + */ + public final int getByteOffset() + { + + // Calculate the offset of the byte buffer + + int pCnt = getParameterCount(); + int pos = WORDCNT + (2 * pCnt) + 3; + return pos; + } + + /** + * Get the SMB command + * + * @return SMB command code. + */ + public final int getCommand() + { + return (int) (m_smbbuf[COMMAND] & 0xFF); + } + + /** + * Determine if normal or long error codes have been returned + * + * @return boolean + */ + public final boolean hasLongErrorCode() + { + if ((getFlags2() & FLG2_LONGERRORCODE) == 0) + return false; + return true; + } + + /** + * Return the saved packet type + * + * @return int + */ + public final int isType() + { + return m_pkttype; + } + + /** + * Check if the packet contains ASCII or Unicode strings + * + * @return boolean + */ + public final boolean isUnicode() + { + return (getFlags2() & FLG2_UNICODE) != 0 ? true : false; + } + + /** + * Check if the packet is using caseless filenames + * + * @return boolean + */ + public final boolean isCaseless() + { + return (getFlags() & FLG_CASELESS) != 0 ? true : false; + } + + /** + * Check if long file names are being used + * + * @return boolean + */ + public final boolean isLongFileNames() + { + return (getFlags2() & FLG2_LONGFILENAMES) != 0 ? true : false; + } + + /** + * Check if long error codes are being used + * + * @return boolean + */ + public final boolean isLongErrorCode() + { + return (getFlags2() & FLG2_LONGERRORCODE) != 0 ? true : false; + } + + /** + * Get the SMB error class + * + * @return SMB error class. + */ + public final int getErrorClass() + { + return (int) m_smbbuf[ERRORCLASS] & 0xFF; + } + + /** + * Get the SMB error code + * + * @return SMB error code. + */ + public final int getErrorCode() + { + return (int) m_smbbuf[ERROR] & 0xFF; + } + + /** + * Get the SMB flags value. + * + * @return SMB flags value. + */ + public final int getFlags() + { + return (int) m_smbbuf[FLAGS] & 0xFF; + } + + /** + * Get the SMB flags2 value. + * + * @return SMB flags2 value. + */ + public final int getFlags2() + { + return (int) DataPacker.getIntelShort(m_smbbuf, FLAGS2); + } + + /** + * Calculate the total used packet length. + * + * @return Total used packet length. + */ + public final int getLength() + { + return (getByteOffset() + getByteCount()) - SIGNATURE; + } + + /** + * Get the long SMB error code + * + * @return Long SMB error code. + */ + public final int getLongErrorCode() + { + return DataPacker.getIntelInt(m_smbbuf, ERRORCODE); + } + + /** + * Get the multiplex identifier. + * + * @return Multiplex identifier. + */ + public final int getMultiplexId() + { + return DataPacker.getIntelShort(m_smbbuf, MID); + } + + /** + * Get a parameter word from the SMB packet. + * + * @param idx Parameter index (zero based). + * @return Parameter word value. + * @exception java.lang.IndexOutOfBoundsException If the parameter index is out of range. + */ + public final int getParameter(int idx) throws java.lang.IndexOutOfBoundsException + { + + // Range check the parameter index + + if (idx > getParameterCount()) + throw new java.lang.IndexOutOfBoundsException(); + + // Calculate the parameter word offset + + int pos = WORDCNT + (2 * idx) + 1; + return (int) (DataPacker.getIntelShort(m_smbbuf, pos) & 0xFFFF); + } + + /** + * Get the specified parameter words, as an int value. + * + * @param idx Parameter index (zero based). + * @param val Parameter value. + */ + public final int getParameterLong(int idx) + { + int pos = WORDCNT + (2 * idx) + 1; + return DataPacker.getIntelInt(m_smbbuf, pos); + } + + /** + * Get the parameter count + * + * @return Parameter word count. + */ + public final int getParameterCount() + { + return (int) m_smbbuf[WORDCNT]; + } + + /** + * Get the process indentifier (PID) + * + * @return Process identifier value. + */ + public final int getProcessId() + { + return DataPacker.getIntelShort(m_smbbuf, PID); + } + + /** + * Get the tree identifier (TID) + * + * @return Tree identifier (TID) + */ + public final int getTreeId() + { + return DataPacker.getIntelShort(m_smbbuf, TID); + } + + /** + * Get the user identifier (UID) + * + * @return User identifier (UID) + */ + public final int getUserId() + { + return DataPacker.getIntelShort(m_smbbuf, UID); + } + + /** + * Return the last sent packet time + * + * @return long + */ + public final long getLastPacketSendTime() + { + return m_lastTxTime; + } + + /** + * Initialize the SMB packet buffer. + */ + private final void InitializeBuffer() + { + + // Set the packet signature + + m_smbbuf[SIGNATURE] = (byte) 0xFF; + m_smbbuf[SIGNATURE + 1] = (byte) 'S'; + m_smbbuf[SIGNATURE + 2] = (byte) 'M'; + m_smbbuf[SIGNATURE + 3] = (byte) 'B'; + } + + /** + * Determine if this packet is an SMB response, or command packet + * + * @return true if this SMB packet is a response, else false + */ + public final boolean isResponse() + { + int resp = getFlags(); + if ((resp & FLG_RESPONSE) != 0) + return true; + return false; + } + + /** + * Check if the response packet is valid, ie. type and flags + * + * @return true if the SMB packet is a response packet and the response is valid, else false. + */ + public final boolean isValidResponse() + { + + // Check if this is a response packet, and the correct type of packet + + if (isResponse() && getCommand() == m_pkttype) + { + + // Check if standard error codes or NT 32-bit error codes are being used + + if ((getFlags2() & FLG2_LONGERRORCODE) == 0) + { + if (getErrorCode() == SMBStatus.Success) + return true; + } + else if (getLongErrorCode() == SMBStatus.NTSuccess) + return true; + } + return false; + } + + /** + * Pack a byte (8 bit) value into the byte area + * + * @param val byte + */ + public final void packByte(byte val) + { + m_smbbuf[m_pos++] = val; + } + + /** + * Pack a byte (8 bit) value into the byte area + * + * @param val int + */ + public final void packByte(int val) + { + m_smbbuf[m_pos++] = (byte) val; + } + + /** + * Pack the specified bytes into the byte area + * + * @param byts byte[] + * @param len int + */ + public final void packBytes(byte[] byts, int len) + { + System.arraycopy(byts, 0, m_smbbuf, m_pos, len); + m_pos += len; + } + + /** + * Pack a string using either ASCII or Unicode into the byte area + * + * @param str String + * @param uni boolean + */ + public final void packString(String str, boolean uni) + { + + // Check for Unicode or ASCII + + if (uni) + { + + // Word align the buffer position, pack the Unicode string + + m_pos = DataPacker.wordAlign(m_pos); + DataPacker.putUnicodeString(str, m_smbbuf, m_pos, true); + m_pos += (str.length() * 2) + 2; + } + else + { + + // Pack the ASCII string + + DataPacker.putString(str, m_smbbuf, m_pos, true); + m_pos += str.length() + 1; + } + } + + /** + * Pack a word (16 bit) value into the byte area + * + * @param val int + */ + public final void packWord(int val) + { + DataPacker.putIntelShort(val, m_smbbuf, m_pos); + m_pos += 2; + } + + /** + * Pack a 32 bit integer value into the byte area + * + * @param val int + */ + public final void packInt(int val) + { + DataPacker.putIntelInt(val, m_smbbuf, m_pos); + m_pos += 4; + } + + /** + * Pack a long integer (64 bit) value into the byte area + * + * @param val long + */ + public final void packLong(long val) + { + DataPacker.putIntelLong(val, m_smbbuf, m_pos); + m_pos += 8; + } + + /** + * Return the current byte area buffer position + * + * @return int + */ + public final int getPosition() + { + return m_pos; + } + + /** + * Set the byte area buffer position + * + * @param pos int + */ + public final void setPosition(int pos) + { + m_pos = pos; + } + + /** + * Unpack a byte value from the byte area + * + * @return int + */ + public final int unpackByte() + { + return (int) m_smbbuf[m_pos++]; + } + + /** + * Unpack a block of bytes from the byte area + * + * @param len int + * @return byte[] + */ + public final byte[] unpackBytes(int len) + { + if (len <= 0) + return null; + + byte[] buf = new byte[len]; + System.arraycopy(m_smbbuf, m_pos, buf, 0, len); + m_pos += len; + return buf; + } + + /** + * Unpack a word (16 bit) value from the byte area + * + * @return int + */ + public final int unpackWord() + { + int val = DataPacker.getIntelShort(m_smbbuf, m_pos); + m_pos += 2; + return val; + } + + /** + * Unpack an integer (32 bit) value from the byte/parameter area + * + * @return int + */ + public final int unpackInt() + { + int val = DataPacker.getIntelInt(m_smbbuf, m_pos); + m_pos += 4; + return val; + } + + /** + * Unpack a long integer (64 bit) value from the byte area + * + * @return long + */ + public final long unpackLong() + { + long val = DataPacker.getIntelLong(m_smbbuf, m_pos); + m_pos += 8; + return val; + } + + /** + * Unpack a string from the byte area + * + * @param uni boolean + * @return String + */ + public final String unpackString(boolean uni) + { + + // Check for Unicode or ASCII + + String ret = null; + + if (uni) + { + + // Word align the current buffer position + + m_pos = DataPacker.wordAlign(m_pos); + ret = DataPacker.getUnicodeString(m_smbbuf, m_pos, 255); + if (ret != null) + m_pos += (ret.length() * 2) + 2; + } + else + { + + // Unpack the ASCII string + + ret = DataPacker.getString(m_smbbuf, m_pos, 255); + if (ret != null) + m_pos += ret.length() + 1; + } + + // Return the string + + return ret; + } + + /** + * Unpack a string from the byte area + * + * @param len int + * @param uni boolean + * @return String + */ + public final String unpackString(int len, boolean uni) + { + + // Check for Unicode or ASCII + + String ret = null; + + if (uni) + { + + // Word align the current buffer position + + m_pos = DataPacker.wordAlign(m_pos); + ret = DataPacker.getUnicodeString(m_smbbuf, m_pos, len); + if (ret != null) + m_pos += (ret.length() * 2); + } + else + { + + // Unpack the ASCII string + + ret = DataPacker.getString(m_smbbuf, m_pos, len); + if (ret != null) + m_pos += ret.length(); + } + + // Return the string + + return ret; + } + + /** + * Check if there is more data in the byte area + * + * @return boolean + */ + public final boolean hasMoreData() + { + if (m_pos < m_endpos) + return true; + return false; + } + + /** + * Receive an SMB response packet. + * + * @param sess NetBIOS session to receive the SMB packet on. + * @exception java.io.IOException If an I/O error occurs. + */ + private final void ReceiveSMB(NetBIOSSession sess) throws java.io.IOException + { + + if (sess.Receive(m_smbbuf, RFCNetBIOSProtocol.TMO) >= MIN_RXLEN) + return; + + // Not enough data received for an SMB header + + throw new java.io.IOException("Short NetBIOS receive"); + } + + /** + * Receive an SMB packet on the spceified SMB session. + * + * @param sess SMB session to receive the packet on. + * @exception java.io.IOException If a network error occurs + * @exception SMBException If an SMB level error occurs + */ + protected final void ReceiveSMB(AuthenticateSession sess) throws java.io.IOException, SMBException + { + + // Call the main receive method + + ReceiveSMB(sess, true); + } + + /** + * Receive an SMB packet on the spceified SMB session. + * + * @param sess SMB session to receive the packet on. + * @param throwErr Flag to indicate if an error is thrown if an error response is received + * @exception java.io.IOException If a network error occurs + * @exception SMBException If an SMB level error occurs + */ + protected final void ReceiveSMB(AuthenticateSession sess, boolean throwErr) throws java.io.IOException, + SMBException + { + + // Get the network session + + NetworkSession netSess = sess.getSession(); + + // Receive the response, other asynchronous responses may be received before the response + // for this request + + boolean rxValid = false; + + while (rxValid == false) + { + + // Receive a response + + if (netSess.Receive(getBuffer(), RFCNetBIOSProtocol.TMO) >= MIN_RXLEN) + { + + // Check if the response is for the current request + + if (getCommand() == m_pkttype) + { + + // Check if a valid SMB response has been received + + if (throwErr == true) + checkForError(); + + // Valid packet received, return to caller + + return; + } + } + else + { + + // Not enough data received for an SMB header + + throw new java.io.IOException("Short NetBIOS receive"); + } + } + } + + /** + * Send the SMB packet on the specified SMB session. + * + * @param sess SMB session to send this packet over. + * @exception java.io.IOException If an I/O error occurs. + */ + protected final void SendSMB(AuthenticateSession sess) throws java.io.IOException + { + + // Update the last send time + + m_lastTxTime = System.currentTimeMillis(); + + // Send the SMB request + + sess.getSession().Send(m_smbbuf, getLength()); + } + + /** + * Set the secondary SMB command + * + * @param cmd Secondary SMB command code. + */ + public final void setAndXCommand(int cmd) + { + + // Set the chained command packet type + + m_smbbuf[ANDXCOMMAND] = (byte) cmd; + m_smbbuf[ANDXRESERVED] = (byte) 0; + + // If the AndX command is disabled clear the offset to the chained packet + + if (cmd == PacketType.NoChainedCommand) + setParameter(1, 0); + } + + /** + * Set the data byte count for this SMB packet + * + * @param cnt Data byte count. + */ + public final void setByteCount(int cnt) + { + int offset = getByteOffset() - 2; + DataPacker.putIntelShort(cnt, m_smbbuf, offset); + } + + /** + * Set the data byte count for this SMB packet + */ + + public final void setByteCount() + { + int offset = getByteOffset() - 2; + int len = m_pos - getByteOffset(); + DataPacker.putIntelShort(len, m_smbbuf, offset); + } + + /** + * Set the data byte area in the SMB packet + * + * @param byts Byte array containing the data to be copied to the SMB packet. + */ + public final void setBytes(byte[] byts) + { + int offset = getByteOffset() - 2; + DataPacker.putIntelShort(byts.length, m_smbbuf, offset); + + offset += 2; + + for (int idx = 0; idx < byts.length; m_smbbuf[offset + idx] = byts[idx++]) + ; + } + + /** + * Set the SMB command + * + * @param cmd SMB command code + */ + public final void setCommand(int cmd) + { + m_pkttype = cmd; + m_smbbuf[COMMAND] = (byte) cmd; + } + + /** + * Set the SMB error class. + * + * @param cl SMB error class. + */ + public final void setErrorClass(int cl) + { + m_smbbuf[ERRORCLASS] = (byte) (cl & 0xFF); + } + + /** + * Set the SMB error code + * + * @param sts SMB error code. + */ + public final void setErrorCode(int sts) + { + m_smbbuf[ERROR] = (byte) (sts & 0xFF); + } + + /** + * Set the SMB flags value. + * + * @param flg SMB flags value. + */ + public final void setFlags(int flg) + { + m_smbbuf[FLAGS] = (byte) flg; + } + + /** + * Set the SMB flags2 value. + * + * @param flg SMB flags2 value. + */ + public final void setFlags2(int flg) + { + DataPacker.putIntelShort(flg, m_smbbuf, FLAGS2); + } + + /** + * Set the multiplex identifier. + * + * @param mid Multiplex identifier + */ + public final void setMultiplexId(int mid) + { + DataPacker.putIntelShort(mid, m_smbbuf, MID); + } + + /** + * Set the specified parameter word. + * + * @param idx Parameter index (zero based). + * @param val Parameter value. + */ + public final void setParameter(int idx, int val) + { + int pos = WORDCNT + (2 * idx) + 1; + DataPacker.putIntelShort(val, m_smbbuf, pos); + } + + /** + * Set the specified parameter words. + * + * @param idx Parameter index (zero based). + * @param val Parameter value. + */ + + public final void setParameterLong(int idx, int val) + { + int pos = WORDCNT + (2 * idx) + 1; + DataPacker.putIntelInt(val, m_smbbuf, pos); + } + + /** + * Set the parameter count + * + * @param cnt Parameter word count. + */ + public final void setParameterCount(int cnt) + { + m_smbbuf[WORDCNT] = (byte) cnt; + } + + /** + * Set the process identifier value (PID). + * + * @param pid Process identifier value. + */ + public final void setProcessId(int pid) + { + DataPacker.putIntelShort(pid, m_smbbuf, PID); + } + + /** + * Set the packet sequence number, for connectionless commands. + * + * @param seq Sequence number. + */ + public final void setSeqNo(int seq) + { + DataPacker.putIntelShort(seq, m_smbbuf, SEQNO); + } + + /** + * Set the session id. + * + * @param sid Session id. + */ + public final void setSID(int sid) + { + DataPacker.putIntelShort(sid, m_smbbuf, SID); + } + + /** + * Set the tree identifier (TID) + * + * @param tid Tree identifier value. + */ + public final void setTreeId(int tid) + { + DataPacker.putIntelShort(tid, m_smbbuf, TID); + } + + /** + * Set the user identifier (UID) + * + * @param uid User identifier value. + */ + public final void setUserId(int uid) + { + DataPacker.putIntelShort(uid, m_smbbuf, UID); + } + + /** + * Align the byte area pointer on an int (32bit) boundary + */ + public final void alignBytePointer() + { + m_pos = DataPacker.longwordAlign(m_pos); + } + + /** + * Reset the byte/parameter pointer area for packing/unpacking data items from the packet + */ + public final void resetBytePointer() + { + m_pos = getByteOffset(); + m_endpos = m_pos + getByteCount(); + } + + /** + * Reset the byte/parameter pointer area for packing/unpacking data items from the packet, and + * align the buffer on an int (32bit) boundary + */ + public final void resetBytePointerAlign() + { + m_pos = DataPacker.longwordAlign(getByteOffset()); + m_endpos = m_pos + getByteCount(); + } + + /** + * Reset the byte/parameter pointer area for packing/unpacking paramaters from the packet + */ + public final void resetParameterPointer() + { + m_pos = PARAMWORDS; + } + + /** + * Set the unpack pointer to the specified offset, for AndX processing + * + * @param off int + * @param len int + */ + public final void setBytePointer(int off, int len) + { + m_pos = off; + m_endpos = m_pos + len; + } + + /** + * Skip a number of bytes in the parameter/byte area + * + * @param cnt int + */ + public final void skipBytes(int cnt) + { + m_pos += cnt; + } + + /** + * Return the flags value as a string + * + * @return String + */ + protected final String getFlagsAsString() + { + + // Get the flags value + + int flags = getFlags(); + if (flags == 0) + return ""; + + StringBuffer str = new StringBuffer(); + if ((flags & FLG_SUBDIALECT) != 0) + str.append("SubDialect,"); + + if ((flags & FLG_CASELESS) != 0) + str.append("Caseless,"); + + if ((flags & FLG_CANONICAL) != 0) + str.append("Canonical,"); + + if ((flags & FLG_OPLOCK) != 0) + str.append("Oplock,"); + + if ((flags & FLG_NOTIFY) != 0) + str.append("Notify,"); + + if ((flags & FLG_RESPONSE) != 0) + str.append("Response,"); + + str.setLength(str.length() - 1); + + return str.toString(); + } + + /** + * Return the flags2 value as a string + * + * @return String + */ + protected final String getFlags2AsString() + { + + // Get the flags2 value + + int flags2 = getFlags2(); + + if (flags2 == 0) + return ""; + + StringBuffer str = new StringBuffer(); + + if ((flags2 & FLG2_LONGFILENAMES) != 0) + str.append("LongFilenames,"); + + if ((flags2 & FLG2_EXTENDEDATTRIB) != 0) + str.append("ExtAttributes,"); + + if ((flags2 & FLG2_READIFEXE) != 0) + str.append("ReadIfEXE,"); + + if ((flags2 & FLG2_LONGERRORCODE) != 0) + str.append("LongErrorCode,"); + + if ((flags2 & FLG2_UNICODE) != 0) + str.append("Unicode,"); + + str.setLength(str.length() - 1); + + return str.toString(); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/server/auth/passthru/TcpipSMBNetworkSession.java b/source/java/org/alfresco/filesys/server/auth/passthru/TcpipSMBNetworkSession.java new file mode 100644 index 0000000000..20709e3432 --- /dev/null +++ b/source/java/org/alfresco/filesys/server/auth/passthru/TcpipSMBNetworkSession.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.auth.passthru; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.alfresco.filesys.netbios.RFCNetBIOSProtocol; +import org.alfresco.filesys.smb.NetworkSession; +import org.alfresco.filesys.smb.TcpipSMB; +import org.alfresco.filesys.util.DataPacker; + +/** + * Native TCP/IP SMB Network Session Class + */ +public class TcpipSMBNetworkSession implements NetworkSession +{ + + // Default socket timeout value + + private static int _defTimeout = 30000; // 30 seconds, in milliseconds + + // Socket used to connect and read/write to remote host + + private Socket m_socket; + + // Input and output data streams, from the socket network connection + + private DataInputStream m_in; + private DataOutputStream m_out; + + // Socket timeout + + private int m_tmo = _defTimeout; + + // Debug enable flag and debug output stream + + private static boolean m_debug = false; + private static PrintStream m_dbg = System.out; + + /** + * Default constructor + */ + public TcpipSMBNetworkSession() + { + } + + /** + * Class constructor + * + * @param tmo Socket timeout, in milliseconds + */ + public TcpipSMBNetworkSession(int tmo) + { + m_tmo = tmo; + } + + /** + * Return the protocol name + * + * @return String + */ + public String getProtocolName() + { + return "Native SMB (port 445)"; + } + + /** + * Open a connection to a remote host + * + * @param toName Host name/address being called + * @param fromName Local host name/address + * @param toAddr Optional address + * @exception IOException + */ + public void Open(String toName, String fromName, String toAddr) throws IOException, UnknownHostException + { + + // Create the socket + + m_socket = new Socket(toName, TcpipSMB.PORT); + + // Enable the timeout on the socket, disable the Nagle algorithm + + m_socket.setSoTimeout(m_tmo); + m_socket.setTcpNoDelay(true); + + // Attach input/output streams to the socket + + m_in = new DataInputStream(m_socket.getInputStream()); + m_out = new DataOutputStream(m_socket.getOutputStream()); + } + + /** + * Determine if the session is connected to a remote host + * + * @return boolean + */ + public boolean isConnected() + { + return m_socket != null ? true : false; + } + + /** + * Check if there is data available on this network session + * + * @return boolean + * @exception IOException + */ + public final boolean hasData() throws IOException + { + + // Check if the connection is active + + if (m_socket == null || m_in == null) + return false; + + // Check if there is data available + + return m_in.available() > 0 ? true : false; + } + + /** + * Receive a data packet from the remote host. + * + * @param buf Byte buffer to receive the data into. + * @param tmo Receive timeout in milliseconds, or zero for no timeout + * @return Length of the received data. + * @exception java.io.IOException I/O error occurred. + */ + public int Receive(byte[] buf, int tmo) throws IOException + { + + // Set the read timeout + + m_socket.setSoTimeout(tmo); + + // Read a data packet of data + + int rdlen = m_in.read(buf, 0, RFCNetBIOSProtocol.HEADER_LEN); + + // Check if a header was received + + if (rdlen < RFCNetBIOSProtocol.HEADER_LEN) + throw new java.io.IOException("TCP/IP SMB Short Read"); + + // Get the packet data length + + int pktlen = DataPacker.getInt(buf, 0); + + // Debug mode + + if (m_debug) + m_dbg.println("TcpSMB: Rx " + pktlen + " bytes"); + + // Read the data part of the packet into the users buffer, this may take + // several reads + + int totlen = 0; + int offset = RFCNetBIOSProtocol.HEADER_LEN; + + while (pktlen > 0) + { + + // Read the data + + rdlen = m_in.read(buf, offset, pktlen); + + // Update the received length and remaining data length + + totlen += rdlen; + pktlen -= rdlen; + + // Update the user buffer offset as more reads will be required + // to complete the data read + + offset += rdlen; + + } // end while reading data + + // Return the received data length, not including the header + + return totlen; + } + + /** + * Send a data packet to the remote host. + * + * @param data Byte array containing the data to be sent. + * @param siz Length of the data to send. + * @return true if the data was sent successfully, else false. + * @exception java.io.IOException I/O error occurred. + */ + public boolean Send(byte[] data, int siz) throws IOException + { + + // Pack the data length as the first four bytes of the packet + + DataPacker.putInt(siz, data, 0); + + // Send the packet to the remote host + + int len = siz + RFCNetBIOSProtocol.HEADER_LEN; + m_out.write(data, 0, len); + return true; + } + + /** + * Close the network session + * + * @exception java.io.IOException I/O error occurred + */ + public void Close() throws IOException + { + + // Close the input/output streams + + if (m_in != null) + { + m_in.close(); + m_in = null; + } + + if (m_out != null) + { + m_out.close(); + m_out = null; + } + + // Close the socket + + if (m_socket != null) + { + m_socket.close(); + m_socket = null; + } + } + + /** + * Enable/disable session debugging output + * + * @param dbg true to enable debugging, else false + */ + public static void setDebug(boolean dbg) + { + m_debug = dbg; + } + + /** + * Return the default socket timeout value + * + * @return int + */ + public static final int getDefaultTimeout() + { + return _defTimeout; + } + + /** + * Set the default socket timeout for new sessions + * + * @param tmo int + */ + public static final void setDefaultTimeout(int tmo) + { + _defTimeout = tmo; + } +} diff --git a/source/java/org/alfresco/filesys/smb/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/smb/repo/ContentIOControlHandler.java new file mode 100644 index 0000000000..ae57950b2f --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/repo/ContentIOControlHandler.java @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.smb.repo; + +import java.io.FileNotFoundException; + +import org.alfresco.filesys.server.SrvSession; +import org.alfresco.filesys.server.filesys.DiskDeviceContext; +import org.alfresco.filesys.server.filesys.FileName; +import org.alfresco.filesys.server.filesys.IOControlNotImplementedException; +import org.alfresco.filesys.server.filesys.NetworkFile; +import org.alfresco.filesys.server.filesys.NotifyChange; +import org.alfresco.filesys.server.filesys.TreeConnection; +import org.alfresco.filesys.smb.NTIOCtl; +import org.alfresco.filesys.smb.SMBException; +import org.alfresco.filesys.smb.SMBStatus; +import org.alfresco.filesys.smb.server.repo.CifsHelper; +import org.alfresco.filesys.smb.server.repo.ContentDiskDriver; +import org.alfresco.filesys.smb.server.repo.IOControlHandler; +import org.alfresco.filesys.util.DataBuffer; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Content Disk Driver I/O Control Handler Class + * + *

Provides the custom I/O control code handling used by the CIFS client interface application. + * + * @author gkspencer + */ +public class ContentIOControlHandler implements IOControlHandler +{ + // Logging + + private static final Log logger = LogFactory.getLog(ContentIOControlHandler.class); + + // Services and helpers + + private CifsHelper cifsHelper; + private TransactionService transactionService; + private NodeService nodeService; + private CheckOutCheckInService checkInOutService; + + private ContentDiskDriver contentDriver; + + /** + * Default constructor + */ + public ContentIOControlHandler() + { + } + + /** + * Initalize the I/O control handler + * + * @param contentDriver ContentDiskDriver + * @param cifsHelper CifsHelper + * @param transService TransactionService + * @param nodeService NodeService + * @param cociService CheckOutCheckInService + */ + public void initialize( ContentDiskDriver contentDriver, CifsHelper cifsHelper, + TransactionService transService, NodeService nodeService, CheckOutCheckInService cociService) + { + this.contentDriver = contentDriver; + this.cifsHelper = cifsHelper; + this.transactionService = transService; + this.nodeService = nodeService; + this.checkInOutService = cociService; + } + + /** + * Process a filesystem I/O control request + * + * @param sess Server session + * @param tree Tree connection. + * @param ctrlCode I/O control code + * @param fid File id + * @param dataBuf I/O control specific input data + * @param isFSCtrl true if this is a filesystem control, or false for a device control + * @param filter if bit0 is set indicates that the control applies to the share root handle + * @return DataBuffer + * @exception IOControlNotImplementedException + * @exception SMBException + */ + public DataBuffer processIOControl(SrvSession sess, TreeConnection tree, int ctrlCode, int fid, DataBuffer dataBuf, + boolean isFSCtrl, int filter) + throws IOControlNotImplementedException, SMBException + { + // Validate the file id + + NetworkFile netFile = tree.findFile(fid); + if ( netFile == null || netFile.isDirectory() == false) + throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter); + + // Split the control code + + int devType = NTIOCtl.getDeviceType(ctrlCode); + int ioFunc = NTIOCtl.getFunctionCode(ctrlCode); + + if ( devType != NTIOCtl.DeviceFileSystem || dataBuf == null) + throw new IOControlNotImplementedException(); + + // Check if the request has a valid signature for an Alfresco CIFS server I/O control + + if ( dataBuf.getLength() < IOControl.Signature.length()) + throw new IOControlNotImplementedException("Bad request length"); + + String sig = dataBuf.getString(IOControl.Signature.length(), false); + + if ( sig == null || sig.compareTo(IOControl.Signature) != 0) + throw new IOControlNotImplementedException("Bad request signature"); + + // Get the node for the parent folder, make sure it is a folder + + NodeRef folderNode = null; + + try + { + folderNode = contentDriver.getNodeForPath(tree, netFile.getFullName()); + + if ( cifsHelper.isDirectory( folderNode) == false) + folderNode = null; + } + catch ( FileNotFoundException ex) + { + folderNode = null; + } + + // If the folder node is not valid return an error + + if ( folderNode == null) + throw new SMBException(SMBStatus.NTErr, SMBStatus.NTAccessDenied); + + // Debug + + if ( logger.isInfoEnabled()) { + logger.info("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf); + logger.info(" Folder nodeRef=" + folderNode); + } + + // Check if the I/O control code is one of our custom codes + + DataBuffer retBuffer = null; + + switch ( ioFunc) + { + // Probe to check if this is an Alfresco CIFS server + + case IOControl.CmdProbe: + + // Return a buffer with the signature + + retBuffer = new DataBuffer(IOControl.Signature.length()); + retBuffer.putFixedString(IOControl.Signature, IOControl.Signature.length()); + retBuffer.putInt(IOControl.StsSuccess); + break; + + // Get file information for a file within the current folder + + case IOControl.CmdFileStatus: + + // Process the file status request + + retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode); + break; + + // Check-in file request + + case IOControl.CmdCheckIn: + + // Process the check-in request + + retBuffer = procIOCheckIn( sess, tree, dataBuf, folderNode, netFile); + break; + + // Check-out file request + + case IOControl.CmdCheckOut: + + // Process the check-out request + + retBuffer = procIOCheckOut( sess, tree, dataBuf, folderNode, netFile); + break; + + // Unknown I/O control code + + default: + throw new IOControlNotImplementedException(); + } + + // Return the reply buffer, may be null + + return retBuffer; + } + + /** + * Process the file status I/O request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @return DataBuffer + */ + private final DataBuffer procIOFileStatus( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode) + { + // Start a transaction + + sess.beginTransaction( transactionService, true); + + // Get the file name from the request + + String fName = reqBuf.getString( true); + logger.info(" File status, fname=" + fName); + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get the node for the file/folder + + NodeRef childNode = null; + + try + { + childNode = cifsHelper.getNodeRef( folderNode, fName); + } + catch (FileNotFoundException ex) + { + } + + // Check if the file/folder was found + + if ( childNode == null) + { + // Return an error response + + respBuf.putInt(IOControl.StsFileNotFound); + return respBuf; + } + + // Check if this is a file or folder node + + if ( cifsHelper.isDirectory( childNode)) + { + // Only return the status and node type for folders + + respBuf.putInt(IOControl.StsSuccess); + respBuf.putInt(IOControl.TypeFolder); + } + else + { + // Indicate that this is a file node + + respBuf.putInt(IOControl.StsSuccess); + respBuf.putInt(IOControl.TypeFile); + + // Check if this file is a working copy + + if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) + { + // Indicate that this is a working copy + + respBuf.putInt(IOControl.True); + + // Get the owner username and file it was copied from + + String owner = (String) nodeService.getProperty( childNode, ContentModel.PROP_WORKING_COPY_OWNER); + String copiedFrom = null; + + if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_COPIEDFROM)) + { + // Get the path of the file the working copy was generated from + + NodeRef fromNode = (NodeRef) nodeService.getProperty( childNode, ContentModel.PROP_COPY_REFERENCE); + if ( fromNode != null) + copiedFrom = (String) nodeService.getProperty( fromNode, ContentModel.PROP_NAME); + } + + // Pack the owner and copied from values + + respBuf.putString(owner != null ? owner : "", true, true); + respBuf.putString(copiedFrom != null ? copiedFrom : "", true, true); + } + else + { + // Not a working copy + + respBuf.putInt(IOControl.False); + } + + // Check the lock status of the file + + if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_LOCKABLE)) + { + // Get the lock type and owner + + String lockTypeStr = (String) nodeService.getProperty( childNode, ContentModel.PROP_LOCK_TYPE); + String lockOwner = null; + + if ( lockTypeStr != null) + lockOwner = (String) nodeService.getProperty( childNode, ContentModel.PROP_LOCK_OWNER); + + // Pack the lock type, and owner if there is a lock on the file + + if ( lockTypeStr == null) + respBuf.putInt(IOControl.LockNone); + else + { + LockType lockType = LockType.valueOf( lockTypeStr); + + respBuf.putInt(lockType == LockType.READ_ONLY_LOCK ? IOControl.LockRead : IOControl.LockWrite); + respBuf.putString(lockOwner != null ? lockOwner : "", true, true); + } + } + else + { + // File is not lockable + + respBuf.putInt(IOControl.LockNone); + } + + // Get the content data details for the file + + ContentData contentData = (ContentData) nodeService.getProperty( childNode, ContentModel.PROP_CONTENT); + + if ( contentData != null) + { + // Get the content mime-type + + String mimeType = contentData.getMimetype(); + + // Pack the content length and mime-type + + respBuf.putInt( IOControl.True); + respBuf.putLong( contentData.getSize()); + respBuf.putString( mimeType != null ? mimeType : "", true, true); + } + else + { + // File does not have any content + + respBuf.putInt( IOControl.False); + } + } + + // Return the response + + return respBuf; + } + + /** + * Process the check in I/O request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @param netFile NetworkFile for the folder + * @return DataBuffer + */ + private final DataBuffer procIOCheckIn( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + NetworkFile netFile) + { + // Start a transaction + + sess.beginTransaction( transactionService, false); + + // Get the file name from the request + + String fName = reqBuf.getString( true); + boolean keepCheckedOut = reqBuf.getInt() == IOControl.True ? true : false; + + logger.info(" CheckIn, fname=" + fName + ", keepCheckedOut=" + keepCheckedOut); + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get the node for the file/folder + + NodeRef childNode = null; + + try + { + childNode = cifsHelper.getNodeRef( folderNode, fName); + } + catch (FileNotFoundException ex) + { + } + + // Check if the file/folder was found + + if ( childNode == null) + { + // Return an error response + + respBuf.putInt(IOControl.StsFileNotFound); + return respBuf; + } + + // Check if this is a file or folder node + + if ( cifsHelper.isDirectory( childNode)) + { + // Return an error status, attempt to check in a folder + + respBuf.putInt(IOControl.StsBadParameter); + } + else + { + // Check if this file is a working copy + + if ( nodeService.hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) + { + try + { + // Check in the file + + checkInOutService.checkin( childNode, null, null, keepCheckedOut); + + // Check in was successful + + respBuf.putInt( IOControl.StsSuccess); + + // Check if there are any file/directory change notify requests active + + DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); + if (diskCtx.hasChangeHandler()) { + + // Build the relative path to the checked in file + + String fileName = FileName.buildPath( netFile.getFullName(), null, fName, FileName.DOS_SEPERATOR); + + // Queue a file deleted change notification + + diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, fileName); + } + } + catch (Exception ex) + { + // Return an error status and message + + respBuf.setPosition( IOControl.Signature.length()); + respBuf.putInt(IOControl.StsError); + respBuf.putString( ex.getMessage(), true, true); + } + } + else + { + // Not a working copy + + respBuf.putInt(IOControl.StsNotWorkingCopy); + } + } + + // Return the response + + return respBuf; + } + + /** + * Process the check out I/O request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @param netFile NetworkFile for the folder + * @return DataBuffer + */ + private final DataBuffer procIOCheckOut( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + NetworkFile netFile) + { + // Start a transaction + + sess.beginTransaction( transactionService, false); + + // Get the file name from the request + + String fName = reqBuf.getString( true); + + logger.info(" CheckOut, fname=" + fName); + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get the node for the file/folder + + NodeRef childNode = null; + + try + { + childNode = cifsHelper.getNodeRef( folderNode, fName); + } + catch (FileNotFoundException ex) + { + } + + // Check if the file/folder was found + + if ( childNode == null) + { + // Return an error response + + respBuf.putInt(IOControl.StsFileNotFound); + return respBuf; + } + + // Check if this is a file or folder node + + if ( cifsHelper.isDirectory( childNode)) + { + // Return an error status, attempt to check in a folder + + respBuf.putInt(IOControl.StsBadParameter); + } + else + { + try + { + // Check out the file + + NodeRef workingCopyNode = checkInOutService.checkout( childNode); + + // Get the working copy file name + + String workingCopyName = (String) nodeService.getProperty( workingCopyNode, ContentModel.PROP_NAME); + + // Check out was successful, pack the working copy name + + respBuf.putInt( IOControl.StsSuccess); + respBuf.putString( workingCopyName, true, true); + + // Check if there are any file/directory change notify requests active + + DiskDeviceContext diskCtx = (DiskDeviceContext) tree.getContext(); + if (diskCtx.hasChangeHandler()) { + + // Build the relative path to the checked in file + + String fileName = FileName.buildPath( netFile.getFullName(), null, workingCopyName, FileName.DOS_SEPERATOR); + + // Queue a file added change notification + + diskCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, fileName); + } + } + catch (Exception ex) + { + // Return an error status and message + + respBuf.setPosition( IOControl.Signature.length()); + respBuf.putInt(IOControl.StsError); + respBuf.putString( ex.getMessage(), true, true); + } + } + + // Return the response + + return respBuf; + } +} diff --git a/source/java/org/alfresco/filesys/smb/repo/IOControl.java b/source/java/org/alfresco/filesys/smb/repo/IOControl.java new file mode 100644 index 0000000000..965f669eb6 --- /dev/null +++ b/source/java/org/alfresco/filesys/smb/repo/IOControl.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Alfresco Network License. You may obtain a + * copy of the License at + * + * http://www.alfrescosoftware.com/legal/ + * + * Please view the license relevant to your network subscription. + * + * BY CLICKING THE "I UNDERSTAND AND ACCEPT" BOX, OR INSTALLING, + * READING OR USING ALFRESCO'S Network SOFTWARE (THE "SOFTWARE"), + * YOU ARE AGREEING ON BEHALF OF THE ENTITY LICENSING THE SOFTWARE + * ("COMPANY") THAT COMPANY WILL BE BOUND BY AND IS BECOMING A PARTY TO + * THIS ALFRESCO NETWORK AGREEMENT ("AGREEMENT") AND THAT YOU HAVE THE + * AUTHORITY TO BIND COMPANY. IF COMPANY DOES NOT AGREE TO ALL OF THE + * TERMS OF THIS AGREEMENT, DO NOT SELECT THE "I UNDERSTAND AND AGREE" + * BOX AND DO NOT INSTALL THE SOFTWARE OR VIEW THE SOURCE CODE. COMPANY + * HAS NOT BECOME A LICENSEE OF, AND IS NOT AUTHORIZED TO USE THE + * SOFTWARE UNLESS AND UNTIL IT HAS AGREED TO BE BOUND BY THESE LICENSE + * TERMS. THE "EFFECTIVE DATE" FOR THIS AGREEMENT SHALL BE THE DAY YOU + * CHECK THE "I UNDERSTAND AND ACCEPT" BOX. + */ +package org.alfresco.filesys.server.smb.repo; + +import org.alfresco.filesys.smb.NTIOCtl; + +/** + * Content Disk Driver I/O Control Codes Class + * + *

contains I/O control codes and status codes used by the content disk driver I/O control + * implementation. + * + * @author gkspencer + */ +public class IOControl +{ + // Custom I/O control codes + + public static final int CmdProbe = NTIOCtl.FsCtlCustom; + public static final int CmdFileStatus = NTIOCtl.FsCtlCustom + 1; + public static final int CmdCheckOut = NTIOCtl.FsCtlCustom + 2; + public static final int CmdCheckIn = NTIOCtl.FsCtlCustom + 3; + + // I/O control request/response signature + + public static final String Signature = "ALFRESCO"; + + // I/O control status codes + + public static final int StsSuccess = 0; + + public static final int StsError = 1; + public static final int StsFileNotFound = 2; + public static final int StsAccessDenied = 3; + public static final int StsBadParameter = 4; + public static final int StsNotWorkingCopy = 5; + + // Boolean field values + + public static final int True = 1; + public static final int False = 0; + + // File status field values + // + // Node type + + public static final int TypeFile = 0; + public static final int TypeFolder = 1; + + // Lock status + + public static final int LockNone = 0; + public static final int LockRead = 1; + public static final int LockWrite = 2; +}