jcifs.smb.SmbSession Maven / Gradle / Ivy
Show all versions of jcifs-krb5-jdk7 Show documentation
/* jcifs smb client library in Java
* Copyright (C) 2000 "Michael B. Allen"
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package jcifs.smb;
import java.util.Vector;
import java.util.Enumeration;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.IOException;
import jcifs.Config;
import jcifs.UniAddress;
import jcifs.netbios.NbtAddress;
import jcifs.util.MD4;
public final class SmbSession {
private static final String LOGON_SHARE =
Config.getProperty( "jcifs.smb.client.logonShare", null );
private static final int LOOKUP_RESP_LIMIT =
Config.getInt( "jcifs.netbios.lookupRespLimit", 3 );
private static final String DOMAIN =
Config.getProperty("jcifs.smb.client.domain", null);
private static final String USERNAME =
Config.getProperty("jcifs.smb.client.username", null);
private static final int CACHE_POLICY =
Config.getInt( "jcifs.netbios.cachePolicy", 60 * 10 ) * 60; /* 10 hours */
static NbtAddress[] dc_list = null;
static long dc_list_expiration;
static int dc_list_counter;
private static NtlmChallenge interrogate( NbtAddress addr ) throws SmbException {
UniAddress dc = new UniAddress( addr );
SmbTransport trans = SmbTransport.getSmbTransport( dc, 0 );
if (USERNAME == null) {
trans.connect();
if (SmbTransport.log.level >= 3)
SmbTransport.log.println(
"Default credentials (jcifs.smb.client.username/password)" +
" not specified. SMB signing may not work propertly." +
" Skipping DC interrogation." );
} else {
SmbSession ssn = trans.getSmbSession( NtlmPasswordAuthentication.DEFAULT );
ssn.getSmbTree( LOGON_SHARE, null ).treeConnect( null, null );
}
return new NtlmChallenge( trans.server.encryptionKey, dc );
}
public static NtlmChallenge getChallengeForDomain()
throws SmbException, UnknownHostException {
if( DOMAIN == null ) {
throw new SmbException( "A domain was not specified" );
}
synchronized (DOMAIN) {
long now = System.currentTimeMillis();
int retry = 1;
do {
if (dc_list_expiration < now) {
NbtAddress[] list = NbtAddress.getAllByName( DOMAIN, 0x1C, null, null );
dc_list_expiration = now + CACHE_POLICY * 1000L;
if (list != null && list.length > 0) {
dc_list = list;
} else { /* keep using the old list */
dc_list_expiration = now + 1000 * 60 * 15; /* 15 min */
if (SmbTransport.log.level >= 2) {
SmbTransport.log.println( "Failed to retrieve DC list from WINS" );
}
}
}
int max = Math.min( dc_list.length, LOOKUP_RESP_LIMIT );
for (int j = 0; j < max; j++) {
int i = dc_list_counter++ % max;
if (dc_list[i] != null) {
try {
return interrogate( dc_list[i] );
} catch (SmbException se) {
if (SmbTransport.log.level >= 2) {
SmbTransport.log.println( "Failed validate DC: " + dc_list[i] );
if (SmbTransport.log.level > 2)
se.printStackTrace( SmbTransport.log );
}
}
dc_list[i] = null;
}
}
/* No DCs found, for retieval of list by expiring it and retry.
*/
dc_list_expiration = 0;
} while (retry-- > 0);
dc_list_expiration = now + 1000 * 60 * 15; /* 15 min */
}
throw new UnknownHostException(
"Failed to negotiate with a suitable domain controller for " + DOMAIN );
}
public static byte[] getChallenge( UniAddress dc )
throws SmbException, UnknownHostException {
return getChallenge(dc, 0);
}
public static byte[] getChallenge( UniAddress dc, int port )
throws SmbException, UnknownHostException {
SmbTransport trans = SmbTransport.getSmbTransport( dc, port );
trans.connect();
return trans.server.encryptionKey;
}
/**
* Authenticate arbitrary credentials represented by the
* NtlmPasswordAuthentication object against the domain controller
* specified by the UniAddress parameter. If the credentials are
* not accepted, an SmbAuthException will be thrown. If an error
* occurs an SmbException will be thrown. If the credentials are
* valid, the method will return without throwing an exception. See the
* last FAQ question.
*
* See also the jcifs.smb.client.logonShare property.
*/
public static void logon( UniAddress dc,
NtlmPasswordAuthentication auth ) throws SmbException {
logon(dc, 0, auth);
}
public static void logon( UniAddress dc, int port,
NtlmPasswordAuthentication auth ) throws SmbException {
SmbTree tree = SmbTransport.getSmbTransport( dc, port ).getSmbSession( auth ).getSmbTree( LOGON_SHARE, null );
if( LOGON_SHARE == null ) {
tree.treeConnect( null, null );
} else {
Trans2FindFirst2 req = new Trans2FindFirst2( "\\", "*", SmbFile.ATTR_DIRECTORY );
Trans2FindFirst2Response resp = new Trans2FindFirst2Response();
tree.send( req, resp );
}
}
/* 0 - not connected
* 1 - connecting
* 2 - connected
* 3 - disconnecting
*/
int connectionState;
int uid;
Vector trees;
// Transport parameters allows trans to be removed from CONNECTIONS
private UniAddress address;
private int port, localPort;
private InetAddress localAddr;
SmbTransport transport = null;
NtlmPasswordAuthentication auth;
long expiration;
String netbiosName = null;
SmbSession( UniAddress address, int port,
InetAddress localAddr, int localPort,
NtlmPasswordAuthentication auth ) {
this.address = address;
this.port = port;
this.localAddr = localAddr;
this.localPort = localPort;
this.auth = auth;
trees = new Vector();
connectionState = 0;
}
synchronized SmbTree getSmbTree( String share, String service ) {
SmbTree t;
if( share == null ) {
share = "IPC$";
}
for( Enumeration e = trees.elements(); e.hasMoreElements(); ) {
t = (SmbTree)e.nextElement();
if( t.matches( share, service )) {
return t;
}
}
t = new SmbTree( this, share, service );
trees.addElement( t );
return t;
}
// >>SmbAuthenticator
// boolean matches( NtlmPasswordAuthentication auth ) {
// return this.auth == auth || this.auth.equals( auth );
// }
// In order to support Extended Security Authentication, we need to compare
// authenticator to decide whether or not reuse a session.
boolean matches(SmbExtendedAuthenticator authenticator,
NtlmPasswordAuthentication auth) {
return matcheObject(this.authenticator, authenticator)
&& matcheObject(this.auth, auth);
}
private boolean matcheObject(Object obj1, Object obj2) {
boolean ret = false;
if (obj1 == null) {
if (obj2 == null) {
ret = true;
}
} else {
ret = obj1.equals(obj2);
}
return ret;
}
// SmbAuthenticator<<
synchronized SmbTransport transport() {
if( transport == null ) {
transport = SmbTransport.getSmbTransport( address, port, localAddr, localPort, null );
}
return transport;
}
void send( ServerMessageBlock request,
ServerMessageBlock response ) throws SmbException {
synchronized (transport()) {
if( response != null ) {
response.received = false;
}
expiration = System.currentTimeMillis() + SmbTransport.SO_TIMEOUT;
sessionSetup( request, response );
if( response != null && response.received ) {
return;
}
if (request instanceof SmbComTreeConnectAndX) {
SmbComTreeConnectAndX tcax = (SmbComTreeConnectAndX)request;
if (netbiosName != null && tcax.path.endsWith("\\IPC$")) {
/* Some pipes may require that the hostname in the tree connect
* be the netbios name. So if we have the netbios server name
* from the NTLMSSP type 2 message, and the share is IPC$, we
* assert that the tree connect path uses the netbios hostname.
*/
tcax.path = "\\\\" + netbiosName + "\\IPC$";
}
}
request.uid = uid;
request.auth = auth;
// >>SmbAuthenticator
request.authenticator = authenticator;
// SmbAuthenticator<<
try {
transport.send( request, response );
} catch (SmbException se) {
if (request instanceof SmbComTreeConnectAndX) {
logoff(true);
}
request.digest = null;
throw se;
}
}
}
void sessionSetup( ServerMessageBlock andx,
ServerMessageBlock andxResponse ) throws SmbException {
synchronized (transport()) {
NtlmContext nctx = null;
SmbException ex = null;
SmbComSessionSetupAndX request;
SmbComSessionSetupAndXResponse response;
byte[] token = new byte[0];
int state = 10;
while (connectionState != 0) {
if (connectionState == 2 || connectionState == 3) // connected or disconnecting
return;
try {
transport.wait();
} catch (InterruptedException ie) {
throw new SmbException(ie.getMessage(), ie);
}
}
connectionState = 1; // trying ...
try {
transport.connect();
/*
* Session Setup And X Request / Response
*/
// >>SmbAuthenticator
// if( transport.log.level >= 4 )
// transport.log.println( "sessionSetup: accountName=" + auth.username + ",primaryDomain=" + auth.domain );
if( transport.log.level >= 4 ) {
if (authenticator == null) {
transport.log.println( "sessionSetup: accountName=" + auth.username + ",primaryDomain=" + auth.domain );
}else{
transport.log.println( "sessionSetup: primaryDomain=" + authenticator.getDomain() );
}
}
// SmbAuthenticator<<
/* We explicitly set uid to 0 here to prevent a new
* SMB_COM_SESSION_SETUP_ANDX from having it's uid set to an
* old value when the session is re-established. Otherwise a
* "The parameter is incorrect" error can occur.
*/
uid = 0;
// >>SmbAuthenticator
if (authenticator != null) {
authenticator.sessionSetup(this, andx, andxResponse);
} else {
// SmbAuthenticator<<
do {
switch (state) {
case 10: /* NTLM */
if (auth != NtlmPasswordAuthentication.ANONYMOUS &&
transport.hasCapability(SmbConstants.CAP_EXTENDED_SECURITY)) {
state = 20; /* NTLMSSP */
break;
}
request = new SmbComSessionSetupAndX( this, andx, auth );
response = new SmbComSessionSetupAndXResponse( andxResponse );
/* Create SMB signature digest if necessary
* Only the first SMB_COM_SESSION_SETUP_ANX with non-null or
* blank password initializes signing.
*/
if (transport.isSignatureSetupRequired( auth )) {
if( auth.hashesExternal && NtlmPasswordAuthentication.DEFAULT_PASSWORD != NtlmPasswordAuthentication.BLANK ) {
/* preauthentication
*/
transport.getSmbSession( NtlmPasswordAuthentication.DEFAULT ).getSmbTree( LOGON_SHARE, null ).treeConnect( null, null );
} else {
byte[] signingKey = auth.getSigningKey(transport.server.encryptionKey);
request.digest = new SigningDigest(signingKey, false);
}
}
request.auth = auth;
try {
transport.send( request, response );
} catch (SmbAuthException sae) {
throw sae;
} catch (SmbException se) {
ex = se;
}
if( response.isLoggedInAsGuest &&
"GUEST".equalsIgnoreCase( auth.username ) == false &&
transport.server.security != SmbConstants.SECURITY_SHARE &&
auth != NtlmPasswordAuthentication.ANONYMOUS) {
throw new SmbAuthException( NtStatus.NT_STATUS_LOGON_FAILURE );
}
if (ex != null)
throw ex;
uid = response.uid;
if( request.digest != null ) {
/* success - install the signing digest */
transport.digest = request.digest;
}
connectionState = 2;
state = 0;
break;
case 20:
if (nctx == null) {
boolean doSigning = (transport.flags2 & ServerMessageBlock.FLAGS2_SECURITY_SIGNATURES) != 0;
nctx = new NtlmContext(auth, doSigning);
}
if (SmbTransport.log.level >= 4)
SmbTransport.log.println(nctx);
if (nctx.isEstablished()) {
netbiosName = nctx.getNetbiosName();
connectionState = 2;
state = 0;
break;
}
try {
token = nctx.initSecContext(token, 0, token.length);
} catch (SmbException se) {
/* We must close the transport or the server will be expecting a
* Type3Message. Otherwise, when we send a Type1Message it will return
* "Invalid parameter".
*/
try { transport.disconnect(true); } catch (IOException ioe) {}
uid = 0;
throw se;
}
if (token != null) {
request = new SmbComSessionSetupAndX(this, null, token);
response = new SmbComSessionSetupAndXResponse(null);
if (transport.isSignatureSetupRequired( auth )) {
byte[] signingKey = nctx.getSigningKey();
if (signingKey != null)
request.digest = new SigningDigest(signingKey, true);
}
request.uid = uid;
uid = 0;
try {
transport.send( request, response );
} catch (SmbAuthException sae) {
throw sae;
} catch (SmbException se) {
ex = se;
/* Apparently once a successfull NTLMSSP login occurs, the
* server will return "Access denied" even if a logoff is
* sent. Unfortunately calling disconnect() doesn't always
* actually shutdown the connection before other threads
* have committed themselves (e.g. InterruptTest example).
*/
try { transport.disconnect(true); } catch (Exception e) {}
}
if( response.isLoggedInAsGuest &&
"GUEST".equalsIgnoreCase( auth.username ) == false) {
throw new SmbAuthException( NtStatus.NT_STATUS_LOGON_FAILURE );
}
if (ex != null)
throw ex;
uid = response.uid;
if (request.digest != null) {
/* success - install the signing digest */
transport.digest = request.digest;
}
token = response.blob;
}
break;
default:
throw new SmbException("Unexpected session setup state: " + state);
}
} while (state != 0);
// >>SmbAuthenticator
}
// SmbAuthenticator<<
} catch (SmbException se) {
logoff(true);
connectionState = 0;
throw se;
} finally {
transport.notifyAll();
}
}
}
void logoff( boolean inError ) {
synchronized (transport()) {
if (connectionState != 2) // not-connected
return;
connectionState = 3; // disconnecting
netbiosName = null;
for( Enumeration e = trees.elements(); e.hasMoreElements(); ) {
SmbTree t = (SmbTree)e.nextElement();
t.treeDisconnect( inError );
}
if( !inError && transport.server.security != ServerMessageBlock.SECURITY_SHARE ) {
/*
* Logoff And X Request / Response
*/
SmbComLogoffAndX request = new SmbComLogoffAndX( null );
request.uid = uid;
try {
transport.send( request, null );
} catch( SmbException se ) {
}
uid = 0;
}
connectionState = 0;
transport.notifyAll();
}
}
public String toString() {
return "SmbSession[accountName=" + auth.username +
",primaryDomain=" + auth.domain +
",uid=" + uid +
",connectionState=" + connectionState + "]";
}
// >>SmbAuthenticator
SmbExtendedAuthenticator authenticator = null;
SmbSession(UniAddress address, int port, InetAddress localAddr,
int localPort, SmbExtendedAuthenticator authenticator,
NtlmPasswordAuthentication auth) {
this(address, port, localAddr, localPort, auth);
this.authenticator = authenticator;
}
void setUid(int uid) {
this.uid = uid;
}
void setSessionSetup(boolean b) {
if (b){
connectionState = 2;
}
}
// >>SmbAuthenticator
}