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(" 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
+ * 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
+ * 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 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;
+}