001: /* jcifs smb client library in Java
002: * Copyright (C) 2000 "Michael B. Allen" <jcifs at samba dot org>
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package jcifs.smb;
020:
021: import java.util.Vector;
022: import java.util.Enumeration;
023: import java.net.InetAddress;
024: import java.net.UnknownHostException;
025: import jcifs.Config;
026: import jcifs.UniAddress;
027: import jcifs.netbios.NbtAddress;
028:
029: /**
030: * The class represents a user's session established with an SMB/CIFS
031: * server. This class is used internally to the jCIFS library however
032: * applications may wish to authenticate aribrary user credentials
033: * with the <tt>logon</tt> method. It is noteworthy that jCIFS does not
034: * support DCE/RPC at this time and therefore does not use the NETLOGON
035: * procedure. Instead, it simply performs a "tree connect" to IPC$ using
036: * the supplied credentials. This is only a subset of the NETLOGON procedure
037: * but is achives the same effect.
038:
039: Note that it is possible to change the resource against which clients
040: are authenticated to be something other than <tt>IPC$</tt> using the
041: <tt>jcifs.smb.client.logonShare</tt> property. This can be used to
042: provide simple group based access control. For example, one could setup
043: the NTLM HTTP Filter with the <tt>jcifs.smb.client.domainController</tt>
044: init parameter set to the name of the server used for authentication. On
045: that host, create a share called JCIFSAUTH and adjust the access control
046: list for that share to permit only the clients that should have access to
047: the target website. Finally, set the <tt>jcifs.smb.client.logonShare</tt>
048: to JCIFSAUTH. This should restrict access to only those clients that have
049: access to the JCIFSAUTH share. The access control on that share can be
050: changed without changing init parameters or reinitializing the webapp.
051: */
052:
053: public final class SmbSession {
054:
055: private static final String LOGON_SHARE = Config.getProperty(
056: "jcifs.smb.client.logonShare", null);
057: private static final int LOOKUP_RESP_LIMIT = Config.getInt(
058: "jcifs.netbios.lookupRespLimit", 3);
059: private static final String DOMAIN = Config.getProperty(
060: "jcifs.smb.client.domain", null);
061: private static final String USERNAME = Config.getProperty(
062: "jcifs.smb.client.username", null);
063: private static final int CACHE_POLICY = Config.getInt(
064: "jcifs.netbios.cachePolicy", 60 * 10) * 60; /* 10 hours */
065:
066: static NbtAddress[] dc_list = null;
067: static long dc_list_expiration;
068: static int dc_list_counter;
069:
070: private static NtlmChallenge interrogate(NbtAddress addr)
071: throws SmbException {
072: UniAddress dc = new UniAddress(addr);
073: SmbTransport trans = SmbTransport.getSmbTransport(dc, 0);
074: if (USERNAME == null) {
075: trans.negotiate();
076: if (SmbTransport.log.level > 2)
077: SmbTransport.log
078: .println("Default credentials (jcifs.smb.client.username/password)"
079: + " not specified. SMB signing may not work propertly."
080: + " Skipping DC interrogation.");
081: } else {
082: SmbSession ssn = trans
083: .getSmbSession(NtlmPasswordAuthentication.DEFAULT);
084: ssn.getSmbTree(LOGON_SHARE, null).treeConnect(null, null);
085: }
086: return new NtlmChallenge(trans.server.encryptionKey, dc);
087: }
088:
089: public static NtlmChallenge getChallengeForDomain()
090: throws SmbException, UnknownHostException {
091: if (DOMAIN == null) {
092: throw new SmbException("A domain was not specified");
093: }
094: synchronized (DOMAIN) {
095: long now = System.currentTimeMillis();
096: if (dc_list_expiration < now) {
097: dc_list_expiration = now + CACHE_POLICY * 1000L;
098: NbtAddress[] list = NbtAddress.getAllByName(DOMAIN,
099: 0x1C, null, null);
100: if (list != null && list.length > 0) {
101: dc_list = list;
102: } else { /* keep using the old list */
103: dc_list_expiration = now + 1000 * 60 * 15; /* 15 min */
104: if (SmbTransport.log.level > 1) {
105: SmbTransport.log
106: .println("Failed to retrieve DC list from WINS");
107: }
108: }
109: }
110:
111: int max = Math.min(dc_list.length, LOOKUP_RESP_LIMIT);
112: for (int j = 0; j < max; j++) {
113: int i = dc_list_counter++ % max;
114: if (dc_list[i] != null) {
115: try {
116: return interrogate(dc_list[i]);
117: } catch (SmbException se) {
118: if (SmbTransport.log.level > 1)
119: SmbTransport.log
120: .println("Failed validate DC: "
121: + dc_list[i] + ": "
122: + se.getMessage());
123: }
124: dc_list[i] = null;
125: }
126: }
127:
128: dc_list_expiration = now + 1000 * 60 * 15; /* 15 min */
129: }
130:
131: throw new UnknownHostException(
132: "Failed to negotiate with a suitable domain controller for "
133: + DOMAIN);
134: }
135:
136: public static byte[] getChallenge(UniAddress dc)
137: throws SmbException, UnknownHostException {
138: return getChallenge(dc, 0);
139: }
140:
141: public static byte[] getChallenge(UniAddress dc, int port)
142: throws SmbException, UnknownHostException {
143: SmbTransport trans = SmbTransport.getSmbTransport(dc, port);
144: trans.negotiate();
145: return trans.server.encryptionKey;
146: }
147:
148: /**
149: * Authenticate arbitrary credentials represented by the
150: * <tt>NtlmPasswordAuthentication</tt> object against the domain controller
151: * specified by the <tt>UniAddress</tt> parameter. If the credentials are
152: * not accepted, an <tt>SmbAuthException</tt> will be thrown. If an error
153: * occurs an <tt>SmbException</tt> will be thrown. If the credentials are
154: * valid, the method will return without throwing an exception. See the
155: * last <a href="../../../faq.html">FAQ</a> question.
156: *
157: * See also the <tt>jcifs.smb.client.logonShare</tt> property.
158: */
159: public static void logon(UniAddress dc,
160: NtlmPasswordAuthentication auth) throws SmbException {
161: logon(dc, 0, auth);
162: }
163:
164: public static void logon(UniAddress dc, int port,
165: NtlmPasswordAuthentication auth) throws SmbException {
166: SmbTree tree = SmbTransport.getSmbTransport(dc, port)
167: .getSmbSession(auth).getSmbTree(LOGON_SHARE, null);
168: if (LOGON_SHARE == null) {
169: tree.treeConnect(null, null);
170: } else {
171: Trans2FindFirst2 req = new Trans2FindFirst2("\\", "*",
172: SmbFile.ATTR_DIRECTORY);
173: Trans2FindFirst2Response resp = new Trans2FindFirst2Response();
174: tree.sendTransaction(req, resp);
175: }
176: }
177:
178: private int uid;
179: private Vector trees;
180: private boolean sessionSetup;
181: // Transport parameters allows trans to be removed from CONNECTIONS
182: private UniAddress address;
183: private int port, localPort;
184: private InetAddress localAddr;
185:
186: SmbTransport transport = SmbTransport.NULL_TRANSPORT;
187: NtlmPasswordAuthentication auth;
188: long expiration;
189:
190: SmbSession(UniAddress address, int port, InetAddress localAddr,
191: int localPort, NtlmPasswordAuthentication auth) {
192: this .address = address;
193: this .port = port;
194: this .localAddr = localAddr;
195: this .localPort = localPort;
196: this .auth = auth;
197: trees = new Vector();
198: }
199:
200: synchronized SmbTree getSmbTree(String share, String service) {
201: SmbTree t;
202:
203: if (share == null) {
204: share = "IPC$";
205: }
206: for (Enumeration e = trees.elements(); e.hasMoreElements();) {
207: t = (SmbTree) e.nextElement();
208: if (t.matches(share, service)) {
209: return t;
210: }
211: }
212: t = new SmbTree(this , share, service);
213: trees.addElement(t);
214: return t;
215: }
216:
217: boolean matches(NtlmPasswordAuthentication auth) {
218: return this .auth == auth || this .auth.equals(auth);
219: }
220:
221: synchronized SmbTransport transport() throws SmbException {
222: if (transport == SmbTransport.NULL_TRANSPORT) {
223: transport = SmbTransport.getSmbTransport(address, port,
224: localAddr, localPort);
225: }
226: return transport;
227: }
228:
229: void sendTransaction(SmbComTransaction request,
230: SmbComTransactionResponse response) throws SmbException {
231: // transactions are not batchable
232: sessionSetup(null, null);
233: request.uid = uid;
234: request.auth = auth;
235: transport().sendTransaction(request, response);
236: }
237:
238: void send(ServerMessageBlock request, ServerMessageBlock response)
239: throws SmbException {
240: if (response != null) {
241: response.received = false;
242: }
243: sessionSetup(request, response);
244: if (response != null && response.received) {
245: return;
246: }
247: request.uid = uid;
248: request.auth = auth;
249: transport().send(request, response);
250: }
251:
252: void sessionSetup(ServerMessageBlock andx,
253: ServerMessageBlock andxResponse) throws SmbException {
254:
255: expiration = System.currentTimeMillis()
256: + SmbTransport.SO_TIMEOUT;
257:
258: synchronized (transport()) {
259: if (sessionSetup) {
260: return;
261: }
262:
263: transport.negotiate();
264:
265: /*
266: * Session Setup And X Request / Response
267: */
268:
269: if (transport.log.level > 3)
270: transport.log.println("sessionSetup: accountName="
271: + auth.username + ",primaryDomain="
272: + auth.domain);
273:
274: SmbComSessionSetupAndX request = new SmbComSessionSetupAndX(
275: this , andx);
276: SmbComSessionSetupAndXResponse response = new SmbComSessionSetupAndXResponse(
277: andxResponse);
278:
279: /* Create SMB signature digest if necessary
280: * Only the first SMB_COM_SESSION_SETUP_ANX with creds other than NULL initializes signing.
281: */
282: if (transport.isSignatureSetupRequired(auth)) {
283: if (auth.hashesExternal
284: && NtlmPasswordAuthentication.DEFAULT_PASSWORD != NtlmPasswordAuthentication.BLANK) {
285: /* preauthentication
286: */
287: transport.getSmbSession(
288: NtlmPasswordAuthentication.DEFAULT)
289: .getSmbTree(LOGON_SHARE, null).treeConnect(
290: null, null);
291: }
292: request.digest = new SigningDigest(transport, auth);
293: }
294:
295: request.auth = auth;
296: transport.send(request, response);
297:
298: if (response.isLoggedInAsGuest
299: && "GUEST".equalsIgnoreCase(auth.username) == false) {
300: throw new SmbAuthException(
301: NtStatus.NT_STATUS_LOGON_FAILURE);
302: }
303:
304: uid = response.uid;
305: sessionSetup = true;
306:
307: if (request.digest != null) {
308: /* success - install the signing digest */
309: transport.digest = request.digest;
310: }
311: }
312: }
313:
314: void logoff(boolean inError) {
315: synchronized (transport) {
316: try {
317: if (sessionSetup == false) {
318: return;
319: }
320:
321: for (Enumeration e = trees.elements(); e
322: .hasMoreElements();) {
323: SmbTree t = (SmbTree) e.nextElement();
324: t.treeDisconnect(inError);
325: }
326:
327: if (transport.server.security == ServerMessageBlock.SECURITY_SHARE) {
328: return;
329: }
330:
331: if (!inError) {
332:
333: /*
334: * Logoff And X Request / Response
335: */
336:
337: SmbComLogoffAndX request = new SmbComLogoffAndX(
338: null);
339: request.uid = uid;
340: try {
341: transport.send(request, null);
342: } catch (SmbException se) {
343: }
344: }
345: sessionSetup = false;
346: } finally {
347: transport = SmbTransport.NULL_TRANSPORT;
348: }
349: }
350: }
351:
352: public String toString() {
353: return "SmbSession[accountName=" + auth.username
354: + ",primaryDomain=" + auth.domain + ",uid=" + uid
355: + ",sessionSetup=" + sessionSetup + "]";
356: }
357: }
|