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