/* jcifs smb client library in Java * Copyright (C) 2000 "Michael B. Allen" <jcifs at samba dot org> * * 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 * <tt>NtlmPasswordAuthentication</tt> object against the domain controller * specified by the <tt>UniAddress</tt> parameter. If the credentials are * not accepted, an <tt>SmbAuthException</tt> will be thrown. If an error * occurs an <tt>SmbException</tt> will be thrown. If the credentials are * valid, the method will return without throwing an exception. See the * last <a href="../../../faq.html">FAQ</a> question. * <p> * See also the <tt>jcifs.smb.client.logonShare</tt> 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 }