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.netbios;
020:
021: import java.net.InetAddress;
022: import java.net.DatagramSocket;
023: import java.net.DatagramPacket;
024: import java.net.UnknownHostException;
025: import java.io.IOException;
026: import java.io.InterruptedIOException;
027: import java.util.HashMap;
028: import java.util.StringTokenizer;
029: import jcifs.Config;
030: import jcifs.util.Hexdump;
031: import jcifs.util.LogStream;
032:
033: class NameServiceClient implements Runnable {
034:
035: static final int DEFAULT_SO_TIMEOUT = 5000;
036: static final int DEFAULT_RCV_BUF_SIZE = 576;
037: static final int DEFAULT_SND_BUF_SIZE = 576;
038: static final int NAME_SERVICE_UDP_PORT = 137;
039: static final int DEFAULT_RETRY_COUNT = 2;
040: static final int DEFAULT_RETRY_TIMEOUT = 3000;
041:
042: static final int RESOLVER_LMHOSTS = 1;
043: static final int RESOLVER_BCAST = 2;
044: static final int RESOLVER_WINS = 3;
045:
046: private static final int SND_BUF_SIZE = Config.getInt(
047: "jcifs.netbios.snd_buf_size", DEFAULT_SND_BUF_SIZE);
048: private static final int RCV_BUF_SIZE = Config.getInt(
049: "jcifs.netbios.rcv_buf_size", DEFAULT_RCV_BUF_SIZE);
050: private static final int SO_TIMEOUT = Config.getInt(
051: "jcifs.netbios.soTimeout", DEFAULT_SO_TIMEOUT);
052: private static final int RETRY_COUNT = Config.getInt(
053: "jcifs.netbios.retryCount", DEFAULT_RETRY_COUNT);
054: private static final int RETRY_TIMEOUT = Config.getInt(
055: "jcifs.netbios.retryTimeout", DEFAULT_RETRY_TIMEOUT);
056: private static final int LPORT = Config.getInt(
057: "jcifs.netbios.lport", 0);
058: private static final InetAddress LADDR = Config.getInetAddress(
059: "jcifs.netbios.laddr", null);
060: private static final String RO = Config
061: .getProperty("jcifs.resolveOrder");
062:
063: private static LogStream log = LogStream.getInstance();
064:
065: private final Object LOCK = new Object();
066:
067: private int lport, closeTimeout;
068: private byte[] snd_buf, rcv_buf;
069: private DatagramSocket socket;
070: private DatagramPacket in, out;
071: private HashMap responseTable = new HashMap();
072: private Thread thread;
073: private int nextNameTrnId = 0;
074: private int[] resolveOrder;
075:
076: InetAddress laddr, baddr;
077:
078: NameServiceClient() {
079: this (LPORT, LADDR);
080: }
081:
082: NameServiceClient(int lport, InetAddress laddr) {
083: this .lport = lport;
084: this .laddr = laddr;
085:
086: try {
087: baddr = Config.getInetAddress("jcifs.netbios.baddr",
088: InetAddress.getByName("255.255.255.255"));
089: } catch (UnknownHostException uhe) {
090: }
091:
092: snd_buf = new byte[SND_BUF_SIZE];
093: rcv_buf = new byte[RCV_BUF_SIZE];
094: out = new DatagramPacket(snd_buf, SND_BUF_SIZE, baddr,
095: NAME_SERVICE_UDP_PORT);
096: in = new DatagramPacket(rcv_buf, RCV_BUF_SIZE);
097:
098: if (RO == null || RO.length() == 0) {
099:
100: /* No resolveOrder has been specified, use the
101: * default which is LMHOSTS,WINS,BCAST,DNS or just
102: * LMHOSTS,BCAST,DNS if jcifs.netbios.wins has not
103: * been specified.
104: */
105:
106: if (NbtAddress.getWINSAddress() == null) {
107: resolveOrder = new int[2];
108: resolveOrder[0] = RESOLVER_LMHOSTS;
109: resolveOrder[1] = RESOLVER_BCAST;
110: } else {
111: resolveOrder = new int[3];
112: resolveOrder[0] = RESOLVER_LMHOSTS;
113: resolveOrder[1] = RESOLVER_WINS;
114: resolveOrder[2] = RESOLVER_BCAST;
115: }
116: } else {
117: int[] tmp = new int[3];
118: StringTokenizer st = new StringTokenizer(RO, ",");
119: int i = 0;
120: while (st.hasMoreTokens()) {
121: String s = st.nextToken().trim();
122: if (s.equalsIgnoreCase("LMHOSTS")) {
123: tmp[i++] = RESOLVER_LMHOSTS;
124: } else if (s.equalsIgnoreCase("WINS")) {
125: if (NbtAddress.getWINSAddress() == null) {
126: if (log.level > 1) {
127: log
128: .println("NetBIOS resolveOrder specifies WINS however the "
129: + "jcifs.netbios.wins property has not been set");
130: }
131: continue;
132: }
133: tmp[i++] = RESOLVER_WINS;
134: } else if (s.equalsIgnoreCase("BCAST")) {
135: tmp[i++] = RESOLVER_BCAST;
136: } else if (s.equalsIgnoreCase("DNS")) {
137: ; // skip
138: } else if (log.level > 1) {
139: log.println("unknown resolver method: " + s);
140: }
141: }
142: resolveOrder = new int[i];
143: System.arraycopy(tmp, 0, resolveOrder, 0, i);
144: }
145: }
146:
147: int getNextNameTrnId() {
148: if ((++nextNameTrnId & 0xFFFF) == 0) {
149: nextNameTrnId = 1;
150: }
151: return nextNameTrnId;
152: }
153:
154: void ensureOpen(int timeout) throws IOException {
155: closeTimeout = 0;
156: if (SO_TIMEOUT != 0) {
157: closeTimeout = Math.max(SO_TIMEOUT, timeout);
158: }
159: // If socket is still good, the new closeTimeout will
160: // be ignored; see tryClose comment.
161: if (socket == null) {
162: socket = new DatagramSocket(lport, laddr);
163: thread = new Thread(this , "JCIFS-NameServiceClient");
164: thread.setDaemon(true);
165: thread.start();
166: }
167: }
168:
169: void tryClose() {
170: synchronized (LOCK) {
171:
172: /* Yes, there is the potential to drop packets
173: * because we might close the socket during a
174: * request. However the chances are slim and the
175: * retry code should ensure the overall request
176: * is serviced. The alternative complicates things
177: * more than I think is worth it.
178: */
179:
180: if (socket != null) {
181: socket.close();
182: socket = null;
183: }
184: thread = null;
185: responseTable.clear();
186: }
187: }
188:
189: public void run() {
190: int nameTrnId;
191: NameServicePacket response;
192:
193: try {
194: while (thread == Thread.currentThread()) {
195: in.setLength(RCV_BUF_SIZE);
196:
197: socket.setSoTimeout(closeTimeout);
198: socket.receive(in);
199:
200: if (log.level > 3)
201: log.println("NetBIOS: new data read from socket");
202:
203: nameTrnId = NameServicePacket.readNameTrnId(rcv_buf, 0);
204: response = (NameServicePacket) responseTable
205: .get(new Integer(nameTrnId));
206: if (response == null || response.received) {
207: continue;
208: }
209: synchronized (response) {
210: response.readWireFormat(rcv_buf, 0);
211: response.received = true;
212:
213: if (log.level > 3) {
214: log.println(response);
215: Hexdump
216: .hexdump(log, rcv_buf, 0, in
217: .getLength());
218: }
219:
220: response.notify();
221: }
222: }
223: } catch (Exception ex) {
224: if (log.level > 2)
225: ex.printStackTrace(log);
226: tryClose();
227: }
228: }
229:
230: void send(NameServicePacket request, NameServicePacket response,
231: int timeout) throws IOException {
232: Integer nid = null;
233: int count = 0;
234:
235: synchronized (response) {
236: do {
237: try {
238: synchronized (LOCK) {
239: request.nameTrnId = getNextNameTrnId();
240: nid = new Integer(request.nameTrnId);
241:
242: out.setAddress(request.addr);
243: out.setLength(request.writeWireFormat(snd_buf,
244: 0));
245: response.received = false;
246:
247: responseTable.put(nid, response);
248: ensureOpen(timeout + 1000);
249: socket.send(out);
250:
251: if (log.level > 3) {
252: log.println(request);
253: Hexdump.hexdump(log, snd_buf, 0, out
254: .getLength());
255: }
256: }
257:
258: response.wait(timeout);
259:
260: } catch (InterruptedException ie) {
261: } finally {
262: responseTable.remove(nid);
263: }
264:
265: if (!response.received && NbtAddress.NBNS.length > 1
266: && NbtAddress.isWINS(request.addr)) {
267: /* Message was sent to WINS but
268: * failed to receive response.
269: * Try a different WINS server.
270: */
271: request.addr = NbtAddress.switchWINS();
272: if (count == 0) {
273: count = NbtAddress.NBNS.length - 1;
274: }
275: }
276: } while (count-- > 0);
277: }
278: }
279:
280: NbtAddress[] getAllByName(Name name, InetAddress addr)
281: throws UnknownHostException {
282: int n;
283: NameQueryRequest request = new NameQueryRequest(name);
284: NameQueryResponse response = new NameQueryResponse();
285:
286: request.addr = addr != null ? addr : NbtAddress
287: .getWINSAddress();
288: request.isBroadcast = request.addr == null;
289:
290: if (request.isBroadcast) {
291: request.addr = baddr;
292: n = RETRY_COUNT;
293: } else {
294: request.isBroadcast = false;
295: n = 1;
296: }
297:
298: do {
299: try {
300: send(request, response, RETRY_TIMEOUT);
301: } catch (IOException ioe) {
302: if (log.level > 1)
303: ioe.printStackTrace(log);
304: throw new UnknownHostException(name.name);
305: }
306:
307: if (response.received && response.resultCode == 0) {
308: return response.addrEntry;
309: }
310: } while (--n > 0 && request.isBroadcast);
311:
312: throw new UnknownHostException(name.name);
313: }
314:
315: NbtAddress getByName(Name name, InetAddress addr)
316: throws UnknownHostException {
317: int n;
318: NameQueryRequest request = new NameQueryRequest(name);
319: NameQueryResponse response = new NameQueryResponse();
320:
321: if (addr != null) { /* UniAddress calls always use this
322: * because it specifies addr
323: */
324: request.addr = addr; /* if addr ends with 255 flag it bcast */
325: request.isBroadcast = (addr.getAddress()[3] == (byte) 0xFF);
326:
327: n = RETRY_COUNT;
328: do {
329: try {
330: send(request, response, RETRY_TIMEOUT);
331: } catch (IOException ioe) {
332: if (log.level > 1)
333: ioe.printStackTrace(log);
334: throw new UnknownHostException(name.name);
335: }
336:
337: if (response.received && response.resultCode == 0) {
338: response.addrEntry[0].hostName.srcHashCode = addr
339: .hashCode();
340: return response.addrEntry[0];
341: }
342: } while (--n > 0 && request.isBroadcast);
343:
344: throw new UnknownHostException(name.name);
345: }
346:
347: /* If a target address to query was not specified explicitly
348: * with the addr parameter we fall into this resolveOrder routine.
349: */
350:
351: for (int i = 0; i < resolveOrder.length; i++) {
352: try {
353: switch (resolveOrder[i]) {
354: case RESOLVER_LMHOSTS:
355: NbtAddress ans = Lmhosts.getByName(name);
356: if (ans != null) {
357: ans.hostName.srcHashCode = 0; // just has to be different
358: // from other methods
359: return ans;
360: }
361: break;
362: case RESOLVER_WINS:
363: case RESOLVER_BCAST:
364: if (resolveOrder[i] == RESOLVER_WINS
365: && name.name != NbtAddress.MASTER_BROWSER_NAME
366: && name.hexCode != 0x1d) {
367: request.addr = NbtAddress.getWINSAddress();
368: request.isBroadcast = false;
369: } else {
370: request.addr = baddr;
371: request.isBroadcast = true;
372: }
373:
374: n = RETRY_COUNT;
375: while (n-- > 0) {
376: try {
377: send(request, response, RETRY_TIMEOUT);
378: } catch (IOException ioe) {
379: if (log.level > 1)
380: ioe.printStackTrace(log);
381: throw new UnknownHostException(name.name);
382: }
383: if (response.received
384: && response.resultCode == 0) {
385:
386: /* Before we return, in anticipation of this address being cached we must
387: * augment the addresses name's hashCode to distinguish those resolved by
388: * Lmhosts, WINS, or BCAST. Otherwise a failed query from say WINS would
389: * get pulled out of the cache for a BCAST on the same name.
390: */
391: response.addrEntry[0].hostName.srcHashCode = request.addr
392: .hashCode();
393: return response.addrEntry[0];
394: } else if (resolveOrder[i] == RESOLVER_WINS) {
395: /* If WINS reports negative, no point in retry
396: */
397: break;
398: }
399: }
400: break;
401: }
402: } catch (IOException ioe) {
403: }
404: }
405: throw new UnknownHostException(name.name);
406: }
407:
408: NbtAddress[] getNodeStatus(NbtAddress addr)
409: throws UnknownHostException {
410: int n, srcHashCode;
411: NodeStatusRequest request;
412: NodeStatusResponse response;
413:
414: response = new NodeStatusResponse(addr);
415: request = new NodeStatusRequest(new Name(
416: NbtAddress.ANY_HOSTS_NAME, 0x00, null));
417: request.addr = addr.getInetAddress();
418:
419: n = RETRY_COUNT;
420: while (n-- > 0) {
421: try {
422: send(request, response, RETRY_TIMEOUT);
423: } catch (IOException ioe) {
424: if (log.level > 1)
425: ioe.printStackTrace(log);
426: throw new UnknownHostException(addr.toString());
427: }
428: if (response.received && response.resultCode == 0) {
429:
430: /* For name queries resolved by different sources (e.g. WINS,
431: * BCAST, Node Status) we need to augment the hashcode generated
432: * for the addresses hostname or failed lookups for one type will
433: * be cached and cause other types to fail even though they may
434: * not be the authority for the name. For example, if a WINS lookup
435: * for FOO fails and caches unknownAddress for FOO, a subsequent
436: * lookup for FOO using BCAST should not fail because of that
437: * name cached from WINS.
438: *
439: * So, here we apply the source addresses hashCode to each name to
440: * make them specific to who resolved the name.
441: */
442:
443: srcHashCode = request.addr.hashCode();
444: for (int i = 0; i < response.addressArray.length; i++) {
445: response.addressArray[i].hostName.srcHashCode = srcHashCode;
446: }
447: return response.addressArray;
448: }
449: }
450: throw new UnknownHostException(addr.hostName.name);
451: }
452: }
|