/*
* Copyright (C) 2006-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.jlan.server.auth.passthru;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.StringTokenizer;
import org.springframework.extensions.config.ConfigElement;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.server.SessionListener;
import org.alfresco.jlan.server.SrvSession;
import org.alfresco.jlan.server.auth.AuthContext;
import org.alfresco.jlan.server.auth.AuthenticatorException;
import org.alfresco.jlan.server.auth.CifsAuthenticator;
import org.alfresco.jlan.server.auth.ClientInfo;
import org.alfresco.jlan.server.auth.ICifsAuthenticator;
import org.alfresco.jlan.server.auth.NTLanManAuthContext;
import org.alfresco.jlan.server.auth.UserAccount;
import org.alfresco.jlan.server.auth.ntlm.NTLM;
import org.alfresco.jlan.server.auth.ntlm.NTLMMessage;
import org.alfresco.jlan.server.auth.ntlm.TargetInfo;
import org.alfresco.jlan.server.auth.ntlm.Type1NTLMMessage;
import org.alfresco.jlan.server.auth.ntlm.Type2NTLMMessage;
import org.alfresco.jlan.server.auth.ntlm.Type3NTLMMessage;
import org.alfresco.jlan.server.auth.spnego.NegTokenInit;
import org.alfresco.jlan.server.auth.spnego.NegTokenTarg;
import org.alfresco.jlan.server.auth.spnego.OID;
import org.alfresco.jlan.server.auth.spnego.SPNEGO;
import org.alfresco.jlan.server.config.InvalidConfigurationException;
import org.alfresco.jlan.server.config.ServerConfiguration;
import org.alfresco.jlan.server.core.NoPooledMemoryException;
import org.alfresco.jlan.server.core.ShareType;
import org.alfresco.jlan.server.core.SharedDevice;
import org.alfresco.jlan.smb.Capability;
import org.alfresco.jlan.smb.Protocol;
import org.alfresco.jlan.smb.SMBStatus;
import org.alfresco.jlan.smb.dcerpc.UUID;
import org.alfresco.jlan.smb.server.SMBServer;
import org.alfresco.jlan.smb.server.SMBSrvException;
import org.alfresco.jlan.smb.server.SMBSrvPacket;
import org.alfresco.jlan.smb.server.SMBSrvSession;
import org.alfresco.jlan.smb.server.VirtualCircuit;
import org.alfresco.jlan.util.DataPacker;
import org.alfresco.jlan.util.HexDump;
/**
* Passthru Authenticator Class
* <p>
* 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 CifsAuthenticator implements SessionListener {
// 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
public final static int MinCheckInterval = 10; // 10 seconds
public final static int MaxCheckInterval = 15 * 60; // 15 minutes
// Passthru keep alive interval
public final static long PassthruKeepAliveInterval = 60000L; // 60 seconds
// NTLM flags mask, used to mask out features that are not supported
private static final int NTLM_FLAGS = NTLM.Flag56Bit + NTLM.Flag128Bit + NTLM.FlagLanManKey + NTLM.FlagNegotiateNTLM
+ NTLM.FlagNegotiateUnicode;
// Passthru servers used to authenticate users
private PassthruServers m_passthruServers;
// SMB server
private SMBServer m_server;
// Sessions that are currently in the negotiate/session setup state
private Hashtable m_sessions;
/**
* Default Constructor
*
* <p>Default to user mode security with encrypted password support.
*/
public PassthruAuthenticator() {
// Allocate the session table
m_sessions = new Hashtable();
// Enable extended security session setup
setExtendedSecurity(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) {
// If the server is in share mode security allow the user access
if ( this.getAccessMode() == SHARE_MODE)
return Writeable;
// Check if the IPC$ share is being accessed
if ( share.getType() == ShareType.ADMINPIPE)
return Writeable;
// Check if the user is allowed to access the specified shared device
//
// If a user does not have access to the requested share the connection will still be
// allowed
// but any attempts to access files or search directories will result in a 'no access
// rights'
// error being returned to the client.
UserAccount user = null;
if ( client != null)
user = getUserDetails(client.getUserName());
if ( user == null) {
// Check if the guest account is enabled
return allowGuest() ? Writeable : NoAccess;
}
else if ( user.hasShare(share.getName()) == false)
return NoAccess;
// Allow user to access this share
return 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) {
// The null session will only be allowed to connect to the IPC$ named pipe share.
if ( client.isNullSession()) {
// Debug
if ( hasDebug())
Debug.println("Null CIFS logon allowed");
return ICifsAuthenticator.AUTH_ALLOW;
}
// 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 ( hasDebug())
Debug.println("Authenticated user " + client.getUserName() + " sts=" + getStatusAsString(authSts));
// Return the guest status
return authSts;
}
// Find the active authentication session details for the server session
PassthruDetails passDetails = (PassthruDetails) m_sessions.get(sess.getUniqueId());
if ( passDetails != null) {
try {
// Authenticate the user by passing the hashed password to the authentication server
// using the session that has already been setup.
AuthenticateSession authSess = passDetails.getAuthenticateSession();
authSess.doSessionSetup(client.getUserName(), client.getANSIPassword(), client.getPassword());
// Check if the user has been logged on as a guest
if ( authSess.isGuest()) {
// Check if the local server allows guest access
if ( allowGuest() == true) {
// Get a guest authentication token
doGuestLogon(client, sess);
// Allow the user access as a guest
authSts = ICifsAuthenticator.AUTH_GUEST;
// Debug
if ( hasDebug())
Debug.println("Passthru authenticate user=" + client.getUserName() + ", GUEST");
}
}
else {
// Allow the user full access to the server
authSts = ICifsAuthenticator.AUTH_ALLOW;
// Debug
if ( hasDebug())
Debug.println("Passthru authenticate user=" + client.getUserName() + ", FULL");
}
}
catch (Exception ex) {
// Debug
Debug.println(ex.getMessage());
}
// Keep the authentication session if the user session is an SMB session, else close the
// session now
if ( (sess instanceof SMBSrvSession) == false) {
// Remove the passthru session from the active list
m_sessions.remove(sess.getUniqueId());
// Close the passthru authentication session
try {
// Close the authentication session
AuthenticateSession authSess = passDetails.getAuthenticateSession();
authSess.CloseSession();
// DEBUG
if ( hasDebug())
Debug.println("Closed auth session, sessId=" + authSess.getSessionId());
}
catch (Exception ex) {
// Debug
Debug.println("Passthru error closing session (auth user) " + ex.getMessage());
}
}
}
else {
// DEBUG
if ( hasDebug())
Debug.println(" No PassthruDetails for " + sess.getUniqueId());
}
// Return the authentication status
return authSts;
}
/**
* Return an authentication context for the new session
*
* @return AuthContext
*/
public AuthContext getAuthContext(SMBSrvSession sess) {
// Make sure the SMB server listener is installed
if ( m_server == null && sess instanceof SMBSrvSession) {
SMBSrvSession smbSess = (SMBSrvSession) sess;
m_server = smbSess.getSMBServer();
// Install the server listener
m_server.addSessionListener(this);
}
// Open a connection to the authentication server, use normal session setup
AuthContext authCtx = null;
try {
AuthenticateSession authSess = m_passthruServers.openSession();
if ( authSess != null) {
// Create an entry in the active sessions table for the new session
PassthruDetails passDetails = new PassthruDetails(sess, authSess);
m_sessions.put(sess.getUniqueId(), passDetails);
// Use the challenge key returned from the authentication server
authCtx = new NTLanManAuthContext(authSess.getEncryptionKey());
sess.setAuthenticationContext(authCtx);
// DEBUG
if ( hasDebug())
Debug.println("Passthru sessId=" + authSess.getSessionId() + ", auth ctx=" + authCtx);
}
}
catch (Exception ex) {
// Debug
Debug.println("Passthru error getting challenge " + ex.getMessage());
}
// Return the authentication context
return authCtx;
}
/**
* Generate the CIFS negotiate response packet, the authenticator should add authentication
* specific fields to the response.
*
* @param sess SMBSrvSession
* @param respPkt SMBSrvPacket
* @param extendedSecurity boolean
* @exception AuthenticatorException
*/
public void generateNegotiateResponse(SMBSrvSession sess, SMBSrvPacket respPkt, boolean extendedSecurity)
throws AuthenticatorException {
// If the client does not support extended security then return a standard negotiate
// response
// with an 8 byte challenge
if ( extendedSecurity == false) {
super.generateNegotiateResponse(sess, respPkt, extendedSecurity);
return;
}
// Make sure the extended security negotiation flag is set
if ( (respPkt.getFlags2() & SMBSrvPacket.FLG2_EXTENDEDSECURITY) == 0)
respPkt.setFlags2(respPkt.getFlags2() + SMBSrvPacket.FLG2_EXTENDEDSECURITY);
// Get the negotiate response byte area position
int pos = respPkt.getByteOffset();
byte[] buf = respPkt.getBuffer();
// Pack the CIFS server GUID into the negotiate response
UUID serverGUID = sess.getSMBServer().getServerGUID();
serverGUID.storeUUID(buf, pos, false);
pos += 16;
// Set the negotiate response length
respPkt.setByteCount(pos - respPkt.getByteOffset());
}
/**
* Process the CIFS session setup request packet and build the session setup response
*
* @param sess SMBSrvSession
* @param reqPkt SMBSrvPacket
* @exception SMBSrvException
*/
public void processSessionSetup(SMBSrvSession sess, SMBSrvPacket reqPkt)
throws SMBSrvException {
// Check that the received packet looks like a valid NT session setup andX request
if ( reqPkt.checkPacketIsValid(12, 0) == false)
throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
// Check if the request is using security blobs or the older hashed password format
if ( reqPkt.getParameterCount() == 13) {
// Process the standard password session setup
super.processSessionSetup(sess, reqPkt);
return;
}
// Extract the session details
int maxBufSize = reqPkt.getParameter(2);
int maxMpx = reqPkt.getParameter(3);
int vcNum = reqPkt.getParameter(4);
int secBlobLen = reqPkt.getParameter(7);
int capabs = reqPkt.getParameterLong(10);
// Extract the client details from the session setup request
int dataPos = reqPkt.getByteOffset();
byte[] buf = reqPkt.getBuffer();
// Determine if ASCII or unicode strings are being used
boolean isUni = reqPkt.isUnicode();
// Make a note of the security blob position
int secBlobPos = dataPos;
// Extract the clients primary domain name string
dataPos += secBlobLen;
reqPkt.setPosition(dataPos);
String domain = "";
if ( reqPkt.hasMoreData()) {
// Extract the callers domain name
domain = reqPkt.unpackString(isUni);
if ( domain == null)
throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
}
// Extract the clients native operating system
String clientOS = "";
if ( reqPkt.hasMoreData()) {
// Extract the callers operating system name
clientOS = reqPkt.unpackString(isUni);
if ( clientOS == null)
throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNonSpecificError, SMBStatus.ErrSrv);
}
// Store the client maximum buffer size, maximum multiplexed requests count and client
// capability flags
sess.setClientMaximumBufferSize(maxBufSize != 0 ? maxBufSize : SMBSrvSession.DefaultBufferSize);
sess.setClientMaximumMultiplex(maxMpx);
sess.setClientCapabilities(capabs);
// Create the client information and store in the session
ClientInfo client = ClientInfo.createInfo("", null);
client.setDomain(domain);
client.setOperatingSystem(clientOS);
client.setLogonType(ClientInfo.LogonNormal);
// Set the remote address, if available
if ( sess.hasRemoteAddress())
client.setClientAddress(sess.getRemoteAddress().getHostAddress());
// Set the process id for this client, for multi-stage logons
client.setProcessId(reqPkt.getProcessId());
// Get the current sesion setup object, or null
Object setupObj = sess.getSetupObject(client.getProcessId());
// Process the security blob
byte[] respBlob = null;
boolean isNTLMSSP = false;
try {
// Check if the blob has the NTLMSSP signature
if ( secBlobLen >= NTLM.Signature.length) {
// Check for the NTLMSSP signature
int idx = 0;
while (idx < NTLM.Signature.length && buf[secBlobPos + idx] == NTLM.Signature[idx])
idx++;
if ( idx == NTLM.Signature.length)
isNTLMSSP = true;
}
// Process the security blob
if ( isNTLMSSP == true) {
// DEBUG
if ( hasDebug())
Debug.println("NT Session setup NTLMSSP, MID=" + reqPkt.getMultiplexId() + ", UID=" + reqPkt.getUserId()
+ ", PID=" + reqPkt.getProcessId());
// Process an NTLMSSP security blob
respBlob = doNtlmsspSessionSetup(sess, client, buf, secBlobPos, secBlobLen, isUni);
}
else {
// Process an SPNEGO security blob
respBlob = doSpnegoSessionSetup(sess, client, buf, secBlobPos, secBlobLen, isUni);
}
}
catch (SMBSrvException ex) {
// Cleanup any stored context
sess.removeSetupObject(client.getProcessId());
// Rethrow the exception
throw ex;
}
// Debug
if ( Debug.EnableInfo && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) {
if ( respBlob == null)
Debug.println("[SMB] User " + client.getUserName() + " logged on "
+ (client != null ? " (type " + client.getLogonTypeString() + ")" : ""));
else
Debug.println("[SMB] Two stage logon (" + (isNTLMSSP ? "NTLMSSP" : "SPNEGO") + ")");
}
// Update the client information if not already set
if ( sess.getClientInformation() == null || sess.getClientInformation().getUserName().length() == 0) {
// Set the client details for the session
sess.setClientInformation(client);
}
// Get the response blob length, it can be null
int respLen = respBlob != null ? respBlob.length : 0;
// Check if there is/was a session setup object stored in the session, this indicates a
// multi-stage session setup so set the status code accordingly
SMBSrvPacket respPkt = reqPkt;
boolean loggedOn = false;
if ( isNTLMSSP == true || sess.hasSetupObject(client.getProcessId()) || setupObj != null) {
// NTLMSSP has two stages, if there is a stored setup object then indicate more
// processing required
if ( sess.hasSetupObject(client.getProcessId()))
respPkt.setLongErrorCode(SMBStatus.NTMoreProcessingRequired);
else {
respPkt.setLongErrorCode(SMBStatus.NTSuccess);
// Indicate that the user is logged on
loggedOn = true;
}
// Set the parameter count then check if the security blob will fit into the current
// packet buffer
respPkt.setParameterCount(4);
int reqLen = respLen + 100; // allow for strings
if ( reqLen > respPkt.getAvailableLength()) {
try {
// Allocate a new buffer for the response
respPkt = sess.getPacketPool().allocatePacket(respPkt.getByteOffset() + reqLen, reqPkt);
}
catch (NoPooledMemoryException ex) {
// DEBUG
if ( Debug.EnableDbg && hasDebug())
Debug.println("Authenticator failed to allocate packet from pool, reqSiz="
+ (respPkt.getByteOffset() + respLen));
// Return a server error to the client
throw new SMBSrvException(SMBStatus.NTInvalidParameter, SMBStatus.SRVNoBuffers, SMBStatus.ErrSrv);
}
}
// Fill in the rest of the packet header
respPkt.setParameter(0, 0xFF); // No chained response
respPkt.setParameter(1, 0); // Offset to chained response
respPkt.setParameter(2, 0); // Action
respPkt.setParameter(3, respLen);
}
else {
// Build a completed session setup response
respPkt.setLongErrorCode(SMBStatus.NTSuccess);
// Build the session setup response SMB
respPkt.setParameterCount(12);
respPkt.setParameter(0, 0xFF); // No chained response
respPkt.setParameter(1, 0); // Offset to chained response
respPkt.setParameter(2, SMBSrvSession.DefaultBufferSize);
respPkt.setParameter(3, SMBSrvSession.NTMaxMultiplexed);
respPkt.setParameter(4, 0); // virtual circuit number
respPkt.setParameterLong(5, 0); // session key
respPkt.setParameter(7, respLen); // security blob length
respPkt.setParameterLong(8, 0); // reserved
respPkt.setParameterLong(10, getServerCapabilities());
// Indicate that the user is logged on
loggedOn = true;
}
// If the user is logged on then allocate a virtual circuit
int uid = 0;
if ( loggedOn == true) {
// Clear any stored session setup object for the logon
sess.removeSetupObject(client.getProcessId());
// Create a virtual circuit for the new logon
VirtualCircuit vc = new VirtualCircuit(vcNum, client);
uid = sess.addVirtualCircuit(vc);
if ( uid == VirtualCircuit.InvalidUID) {
// DEBUG
if ( hasDebug() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE))
Debug.println("Failed to allocate UID for virtual circuit, " + vc);
// Failed to allocate a UID
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.DOSAccessDenied, SMBStatus.ErrDos);
}
else if ( hasDebug() && sess.hasDebug(SMBSrvSession.DBG_NEGOTIATE)) {
// DEBUG
Debug.println("Allocated UID=" + uid + " for VC=" + vc);
}
}
// Common session setup response
respPkt.setCommand(reqPkt.getCommand());
respPkt.setByteCount(0);
respPkt.setTreeId(0);
respPkt.setUserId(uid);
// Set the various flags
int flags = respPkt.getFlags();
flags &= ~SMBSrvPacket.FLG_CASELESS;
respPkt.setFlags(flags);
int flags2 = SMBSrvPacket.FLG2_LONGFILENAMES + SMBSrvPacket.FLG2_EXTENDEDSECURITY + SMBSrvPacket.FLG2_LONGERRORCODE;
if ( isUni)
flags2 += SMBSrvPacket.FLG2_UNICODE;
respPkt.setFlags2(flags2);
// Pack the security blob
int pos = respPkt.getByteOffset();
buf = respPkt.getBuffer();
if ( respBlob != null) {
System.arraycopy(respBlob, 0, buf, pos, respBlob.length);
pos += respBlob.length;
}
// Pack the OS, dialect and domain name strings
if ( isUni)
pos = DataPacker.wordAlign(pos);
pos = DataPacker.putString("Java", buf, pos, true, isUni);
pos = DataPacker.putString("Alfresco CIFS Server " + sess.getServer().isVersion(), buf, pos, true, isUni);
pos = DataPacker.putString(sess.getSMBServer().getCIFSConfiguration().getDomainName(), buf, pos, true, isUni);
respPkt.setByteCount(pos - respPkt.getByteOffset());
}
/**
* Process an NTLMSSP security blob
*
* @param sess SMBSrvSession
* @param client ClientInfo
* @param secbuf byte[]
* @param secpos int
* @param seclen int
* @param unicode boolean
* @exception SMBSrvException
*/
private final byte[] doNtlmsspSessionSetup(SMBSrvSession sess, ClientInfo client, byte[] secbuf, int secpos, int seclen,
boolean unicode)
throws SMBSrvException {
// Determine the NTLmSSP message type
int msgType = NTLMMessage.isNTLMType(secbuf, secpos);
byte[] respBlob = null;
if ( msgType == -1) {
// DEBUG
if ( hasDebug()) {
Debug.println("Invalid NTLMSSP token received");
Debug.println(" Token=" + HexDump.hexString(secbuf, secpos, seclen, " "));
}
// Return a logon failure status
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
}
// Check for a type 1 NTLMSSP message
else if ( msgType == NTLM.Type1) {
// Create the type 1 NTLM message from the token
Type1NTLMMessage type1Msg = new Type1NTLMMessage(secbuf, secpos, seclen);
// Build the type 2 NTLM response message
//
// Get the flags from the client request and mask out unsupported features
int ntlmFlags = type1Msg.getFlags() & NTLM_FLAGS;
// Generate a challenge for the response
NTLanManAuthContext ntlmCtx = (NTLanManAuthContext) getAuthContext(sess);
if ( ntlmCtx == null)
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
// Build a type2 message to send back to the client, containing the challenge
String domain = sess.getSMBServer().getServerName();
List tList = new ArrayList();
tList.add(new TargetInfo(NTLM.TargetDomain, domain));
tList.add(new TargetInfo(NTLM.TargetServer, sess.getServerName()));
tList.add(new TargetInfo(NTLM.TargetDNSDomain, domain));
tList.add(new TargetInfo(NTLM.TargetFullDNS, domain));
ntlmFlags = NTLM.FlagChallengeAccept + NTLM.FlagRequestTarget + NTLM.FlagNegotiateNTLM + NTLM.FlagNegotiateUnicode
+ NTLM.FlagKeyExchange + NTLM.FlagTargetInfo + NTLM.Flag56Bit;
// NTLM.FlagAlwaysSign + NTLM.FlagNegotiateSign +
Type2NTLMMessage type2Msg = new Type2NTLMMessage();
type2Msg.buildType2(ntlmFlags, domain, ntlmCtx.getChallenge(), null, tList);
// Store the type 2 message in the session until the session setup is complete
sess.setSetupObject(client.getProcessId(), type2Msg);
// Set the response blob using the type 2 message
respBlob = type2Msg.getBytes();
}
else if ( msgType == NTLM.Type3) {
// Create the type 3 NTLM message from the token
Type3NTLMMessage type3Msg = new Type3NTLMMessage(secbuf, secpos, seclen, unicode);
// Make sure a type 2 message was stored in the first stage of the session setup
if ( sess.hasSetupObject(client.getProcessId()) == false
|| sess.getSetupObject(client.getProcessId()) instanceof Type2NTLMMessage == false) {
// Clear the setup object
sess.removeSetupObject(client.getProcessId());
// Return a logon failure
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
}
// Determine if the client sent us NTLMv1 or NTLMv2
if ( type3Msg.hasFlag(NTLM.Flag128Bit) && type3Msg.hasFlag(NTLM.FlagNTLM2Key)) {
// Debug
if ( hasDebug())
Debug.println("Received NTLMSSP/NTLMv2, not supported");
// Return a logon failure
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
}
else {
// Looks like an NTLMv1 blob
doNTLMv1Logon(sess, client, type3Msg);
// Debug
if ( hasDebug())
Debug.println("Logged on using NTLMSSP/NTLMv1");
}
}
// Return the response blob
return respBlob;
}
/**
* Perform an NTLMv1 logon using the NTLMSSP type3 message
*
* @param sess SMBSrvSession
* @param client ClientInfo
* @param type3Msg Type3NTLMMessage
* @exception SMBSrvException
*/
private final void doNTLMv1Logon(SMBSrvSession sess, ClientInfo client, Type3NTLMMessage type3Msg)
throws SMBSrvException {
// Get the type 2 message that contains the challenge sent to the client
Type2NTLMMessage type2Msg = (Type2NTLMMessage) sess.getSetupObject(client.getProcessId());
sess.removeSetupObject(client.getProcessId());
// Get the NTLM logon details
String userName = type3Msg.getUserName();
// Check for a null logon
if ( userName.length() == 0) {
// DEBUG
if ( hasDebug())
Debug.println("Null logon");
// Indicate a null logon in the client information
client.setLogonType(ClientInfo.LogonNull);
return;
}
// Find the active authentication session details for the server session
PassthruDetails passDetails = (PassthruDetails) m_sessions.get(sess.getUniqueId());
if ( passDetails != null) {
try {
// Authenticate the user by passing the hashed password to the authentication server
// using the session that has already been setup.
AuthenticateSession authSess = passDetails.getAuthenticateSession();
authSess.doSessionSetup(userName, type3Msg.getLMHash(), type3Msg.getNTLMHash());
// Check if the user has been logged on as a guest
if ( authSess.isGuest()) {
// Check if the local server allows guest access
if ( allowGuest() == true) {
// Get a guest authentication token
doGuestLogon(client, sess);
// Debug
if ( hasDebug())
Debug.println("Passthru authenticate user=" + userName + ", GUEST");
}
}
else {
// Indicate that the client is logged on
client.setLogonType(ClientInfo.LogonNormal);
// Debug
if ( hasDebug())
Debug.println("Passthru authenticate user=" + userName + ", FULL");
}
// Update the client details
client.setDomain(type3Msg.getDomain());
client.setUserName(userName);
}
catch (Exception ex) {
// Debug
Debug.println(ex.getMessage());
// Indicate logon failure
throw new SMBSrvException(SMBStatus.NTErr, SMBStatus.NTLogonFailure);
}
finally {
// Remove the passthru session from the active list
m_sessions.remove(sess.getUniqueId());
// Close the passthru authentication session
try {
// Close the authentication session
AuthenticateSession authSess = passDetails.getAuthenticateSession();
authSess.CloseSession();
// DEBUG
if ( hasDebug())
Debug.println("Closed auth session, sessId=" + authSess.getSessionId());
}
catch (Exception ex) {
// Debug
Debug.println("Passthru error closing session (auth user) " + ex.getMessage());
}
}
}
else {
// DEBUG
if ( hasDebug())
Debug.println(" No PassthruDetails for " + sess.getUniqueId());
// Indicate logon failure
throw new SMBSrvException(SMBStatus.NTErr, SMBStatus.NTLogonFailure);
}
}
/**
* Process an SPNEGO security blob
*
* @param sess SMBSrvSession
* @param client ClientInfo
* @param secbuf byte[]
* @param secpos int
* @param seclen int
* @param unicode boolean
* @exception SMBSrvException
*/
private final byte[] doSpnegoSessionSetup(SMBSrvSession sess, ClientInfo client, byte[] secbuf, int secpos, int seclen,
boolean unicode)
throws SMBSrvException {
// Check the received token type, if it is a target token and there is a stored session
// setup object, this is the second
// stage of an NTLMSSP session setup that is wrapped with SPNEGO
int tokType = -1;
try {
tokType = SPNEGO.checkTokenType(secbuf, secpos, seclen);
}
catch (IOException ex) {
}
// Check for the second stage of an NTLMSSP logon
NegTokenTarg negTarg = null;
if ( tokType == SPNEGO.NegTokenTarg && sess.hasSetupObject(client.getProcessId())
&& sess.getSetupObject(client.getProcessId()) instanceof Type2NTLMMessage) {
// Get the NTLMSSP blob from the NegTokenTarg blob
NegTokenTarg negToken = new NegTokenTarg();
try {
// Decode the security blob
negToken.decode(secbuf, secpos, seclen);
}
catch (IOException ex) {
// Log the error
if ( hasDebug())
Debug.println("Passthru error on session startup: " + ex.getMessage());
// Return a logon failure status
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
}
// Get the second stage NTLMSSP blob
byte[] ntlmsspBlob = negToken.getResponseToken();
// Perform an NTLMSSP session setup
byte[] ntlmsspRespBlob = doNtlmsspSessionSetup(sess, client, ntlmsspBlob, 0, ntlmsspBlob.length, unicode);
// NTLMSSP is a two stage process, set the SPNEGO status
int spnegoSts = SPNEGO.AcceptCompleted;
if ( sess.hasSetupObject(client.getProcessId()))
spnegoSts = SPNEGO.AcceptIncomplete;
// Package the NTLMSSP response in an SPNEGO response
negTarg = new NegTokenTarg(spnegoSts, null, ntlmsspRespBlob);
}
else if ( tokType == SPNEGO.NegTokenInit) {
// Parse the SPNEGO security blob to get the Kerberos ticket
NegTokenInit negToken = new NegTokenInit();
try {
// Decode the security blob
negToken.decode(secbuf, secpos, seclen);
}
catch (IOException ex) {
// Log the error
if ( hasDebug())
Debug.println("Passthru error on session startup: " + ex.getMessage());
// Return a logon failure status
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
}
// Determine the authentication mechanism the client is using and logon
String oidStr = null;
if ( negToken.numberOfOids() > 0)
oidStr = negToken.getOidAt(0).toString();
if ( oidStr != null && oidStr.equals(OID.ID_NTLMSSP)) {
// NTLMSSP logon, get the NTLMSSP security blob that is inside the SPNEGO blob
byte[] ntlmsspBlob = negToken.getMechtoken();
// Perform an NTLMSSP session setup
byte[] ntlmsspRespBlob = doNtlmsspSessionSetup(sess, client, ntlmsspBlob, 0, ntlmsspBlob.length, unicode);
// NTLMSSP is a two stage process, set the SPNEGO status
int spnegoSts = SPNEGO.AcceptCompleted;
if ( sess.hasSetupObject(client.getProcessId()))
spnegoSts = SPNEGO.AcceptIncomplete;
// Package the NTLMSSP response in an SPNEGO response
negTarg = new NegTokenTarg(spnegoSts, OID.NTLMSSP, ntlmsspRespBlob);
}
else {
// Debug
if ( hasDebug()) {
Debug.println("No matching authentication OID found");
Debug.println(" " + negToken.toString());
}
// No valid authentication mechanism
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
}
}
else {
// Unknown SPNEGO token type
if ( hasDebug()) {
Debug.println("Unknown SPNEGO token type");
}
// Return a logon failure status
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
}
// Generate the NegTokenTarg blob
byte[] respBlob = null;
try {
// Generate the response blob
respBlob = negTarg.encode();
}
catch (IOException ex) {
// Debug
if ( hasDebug()) {
Debug.println("Failed to encode NegTokenTarg: " + ex.getMessage());
}
// Failed to build response blob
throw new SMBSrvException(SMBStatus.NTLogonFailure, SMBStatus.ErrDos, SMBStatus.DOSAccessDenied);
}
// Return the SPNEGO response blob
return respBlob;
}
/**
* 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 passthru session protocol order has been specified
ConfigElement protoOrder = params.getChild("protocolOrder");
if ( protoOrder != null) {
// Parse the protocol order list
StringTokenizer tokens = new StringTokenizer(protoOrder.getValue(), ",");
int primaryProto = Protocol.None;
int secondaryProto = Protocol.None;
// There should only be one or two tokens
if ( tokens.countTokens() > 2)
throw new InvalidConfigurationException("Invalid protocol order list, " + protoOrder.getValue());
// Get the primary protocol
if ( tokens.hasMoreTokens()) {
// Parse the primary protocol
String primaryStr = tokens.nextToken();
if ( primaryStr.equalsIgnoreCase("TCPIP"))
primaryProto = Protocol.NativeSMB;
else if ( primaryStr.equalsIgnoreCase("NetBIOS"))
primaryProto = Protocol.TCPNetBIOS;
else
throw new InvalidConfigurationException("Invalid protocol type, " + primaryStr);
// Check if there is a secondary protocol, and validate
if ( tokens.hasMoreTokens()) {
// Parse the secondary protocol
String secondaryStr = tokens.nextToken();
if ( secondaryStr.equalsIgnoreCase("TCPIP") && primaryProto != Protocol.NativeSMB)
secondaryProto = Protocol.NativeSMB;
else if ( secondaryStr.equalsIgnoreCase("NetBIOS") && primaryProto != Protocol.TCPNetBIOS)
secondaryProto = Protocol.TCPNetBIOS;
else
throw new InvalidConfigurationException("Invalid secondary protocol, " + secondaryStr);
}
}
// Set the protocol order used for passthru authentication sessions
AuthSessionFactory.setProtocolOrder(primaryProto, secondaryProto);
// DEBUG
if ( hasDebug())
Debug.println("Protocol order primary=" + Protocol.asString(primaryProto) + ", secondary="
+ Protocol.asString(secondaryProto));
}
// Check if the offline check interval has been specified
ConfigElement checkInterval = params.getChild("offlineCheckInterval");
if ( checkInterval != null) {
try {
// Validate the check interval value
int offlineCheck = Integer.parseInt(checkInterval.getValue());
// Range check the value
if ( offlineCheck < MinCheckInterval || offlineCheck > MaxCheckInterval)
throw new InvalidConfigurationException("Invalid offline check interval, valid range is " + MinCheckInterval
+ " to " + MaxCheckInterval);
// Set the offline check interval for offline passthru servers
m_passthruServers = new PassthruServers(offlineCheck);
// DEBUG
if ( hasDebug())
Debug.println("Using offline check interval of " + offlineCheck + " seconds");
}
catch (NumberFormatException ex) {
throw new InvalidConfigurationException("Invalid offline check interval specified");
}
}
else {
// Create the passthru server list with the default offline check interval
m_passthruServers = new PassthruServers();
}
// Enable passthru servers debugging, if enabled for the authenticator
if ( hasDebug())
m_passthruServers.setDebug(true);
// Check if the session timeout has been specified
ConfigElement sessTmoElem = params.getChild("Timeout");
if ( sessTmoElem != null) {
try {
// Validate the session timeout value
int sessTmo = Integer.parseInt(sessTmoElem.getValue());
// Range check the timeout
if ( sessTmo < MinSessionTmo || sessTmo > MaxSessionTmo)
throw new InvalidConfigurationException("Invalid session timeout, valid range is " + MinSessionTmo + " to "
+ MaxSessionTmo);
// Set the session timeout for connecting to an authentication server
m_passthruServers.setConnectionTimeout(sessTmo);
}
catch (NumberFormatException ex) {
throw new InvalidConfigurationException("Invalid timeout value specified");
}
}
// Check if a server name has been specified
String srvList = null;
ConfigElement srvNamesElem = params.getChild("Server");
if ( srvNamesElem != null && srvNamesElem.getValue().length() > 0) {
// Check if the server name was already set
if ( srvList != null)
throw new InvalidConfigurationException("Set passthru server via local server or specify name");
// Get the passthru authenticator server name
srvList = srvNamesElem.getValue();
}
// If the passthru server name has been set initialize the passthru connection
if ( srvList != null) {
// Initialize using a list of server names/addresses
m_passthruServers.setServerList(srvList);
}
else {
// Get the domain/workgroup name
String domainName = null;
ConfigElement domNameElem = params.getChild("Domain");
if ( domNameElem != null && domNameElem.getValue().length() > 0) {
// Check if the authentication server has already been set, ie. server name was also
// specified
if ( srvList != null)
throw new InvalidConfigurationException("Specify server or domain name for passthru authentication");
domainName = domNameElem.getValue();
}
// If the domain name has been set initialize the passthru connection
if ( domainName != null) {
// Initialize using the domain
try {
m_passthruServers.setDomain(domainName);
}
catch (IOException ex) {
throw new InvalidConfigurationException("Failed to set domain, " + ex.getMessage());
}
}
}
// Check if we have an authentication server
if ( m_passthruServers.getTotalServerCount() == 0)
throw new InvalidConfigurationException("No valid authentication servers found for passthru");
// Install the SMB server listener so we receive callbacks when sessions are
// opened/closed on the SMB server
SMBServer smbServer = (SMBServer) config.findServer("SMB");
if ( smbServer != null)
smbServer.addSessionListener(this);
}
/**
* Return the server capability flags
*
* @return int
*/
public int getServerCapabilities() {
return Capability.Unicode + Capability.RemoteAPIs + Capability.NTSMBs + Capability.NTFind + Capability.NTStatus
+ Capability.LargeFiles + Capability.LargeRead + Capability.LargeWrite + Capability.ExtendedSecurity;
}
/**
* Close the authenticator, perform cleanup
*/
public void closeAuthenticator() {
// Close the passthru authentication server list
if ( m_passthruServers != null)
m_passthruServers.shutdown();
}
/**
* SMB server session closed notification
*
* @param sess SrvSession
*/
public void sessionClosed(SrvSession sess) {
// Check if there is an active session to the authentication server for this local
// session
PassthruDetails passDetails = (PassthruDetails) m_sessions.get(sess.getUniqueId());
if ( passDetails != null) {
// Remove the passthru session from the active list
m_sessions.remove(sess.getUniqueId());
// Close the passthru authentication session
try {
// Close the authentication session
AuthenticateSession authSess = passDetails.getAuthenticateSession();
authSess.CloseSession();
// DEBUG
if ( hasDebug())
Debug.println("Closed auth session, sessId=" + authSess.getSessionId());
}
catch (Exception ex) {
// Debug
if ( hasDebug())
Debug.println("Passthru error closing session (closed) " + ex.getMessage());
}
}
}
/**
* SMB server session created notification
*
* @param sess SrvSession
*/
public void sessionCreated(SrvSession sess) {
}
/**
* User successfully logged on notification
*
* @param sess SrvSession
*/
public void sessionLoggedOn(SrvSession sess) {
// Check if there is an active session to the authentication server for this local
// session
PassthruDetails passDetails = (PassthruDetails) m_sessions.get(sess.getUniqueId());
if ( passDetails != null) {
// Remove the passthru session from the active list
m_sessions.remove(sess.getUniqueId());
// Close the passthru authentication session
try {
// Close the authentication session
AuthenticateSession authSess = passDetails.getAuthenticateSession();
authSess.CloseSession();
// DEBUG
if ( hasDebug())
Debug.println("Closed auth session, sessId=" + authSess.getSessionId());
}
catch (Exception ex) {
// Debug
if ( hasDebug())
Debug.println("Passthru error closing session (logon) " + ex.getMessage());
}
}
}
}