1 // Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2
3 package org.xbill.DNS;
4
5 import java.io.*;
6 import java.lang.reflect.*;
7 import java.util.*;
8
9 /**
10 * A class that tries to locate name servers and the search path to
11 * be appended to unqualified names.
12 *
13 * The following are attempted, in order, until one succeeds.
14 * <UL>
15 * <LI>The properties 'dns.server' and 'dns.search' (comma delimited lists)
16 * are checked. The servers can either be IP addresses or hostnames
17 * (which are resolved using Java's built in DNS support).
18 * <LI>The sun.net.dns.ResolverConfiguration class is queried.
19 * <LI>On Unix, /etc/resolv.conf is parsed.
20 * <LI>On Windows, ipconfig/winipcfg is called and its output parsed. This
21 * may fail for non-English versions on Windows.
22 * <LI>"localhost" is used as the nameserver, and the search path is empty.
23 * </UL>
24 *
25 * These routines will be called internally when creating Resolvers/Lookups
26 * without explicitly specifying server names, and can also be called
27 * directly if desired.
28 *
29 * @author Brian Wellington
30 * @author <a href="mailto:yannick@meudal.net">Yannick Meudal</a>
31 * @author <a href="mailto:arnt@gulbrandsen.priv.no">Arnt Gulbrandsen</a>
32 */
33
34 public class ResolverConfig {
35
36 private String [] servers = null;
37 private Name [] searchlist = null;
38 private int ndots = -1;
39
40 private static ResolverConfig currentConfig;
41
42 static {
refresh()43 refresh();
44 }
45
46 public
ResolverConfig()47 ResolverConfig() {
48 if (findProperty())
49 return;
50 if (findSunJVM())
51 return;
52 if (servers == null || searchlist == null) {
53 String OS = System.getProperty("os.name");
54 String vendor = System.getProperty("java.vendor");
55 if (OS.indexOf("Windows") != -1) {
56 if (OS.indexOf("95") != -1 ||
57 OS.indexOf("98") != -1 ||
58 OS.indexOf("ME") != -1)
59 find95();
60 else
61 findNT();
62 } else if (OS.indexOf("NetWare") != -1) {
63 findNetware();
64 } else if (vendor.indexOf("Android") != -1) {
65 findAndroid();
66 } else {
67 findUnix();
68 }
69 }
70 }
71
72 private void
addServer(String server, List list)73 addServer(String server, List list) {
74 if (list.contains(server))
75 return;
76 if (Options.check("verbose"))
77 System.out.println("adding server " + server);
78 list.add(server);
79 }
80
81 private void
addSearch(String search, List list)82 addSearch(String search, List list) {
83 Name name;
84 if (Options.check("verbose"))
85 System.out.println("adding search " + search);
86 try {
87 name = Name.fromString(search, Name.root);
88 }
89 catch (TextParseException e) {
90 return;
91 }
92 if (list.contains(name))
93 return;
94 list.add(name);
95 }
96
97 private int
parseNdots(String token)98 parseNdots(String token) {
99 token = token.substring(6);
100 try {
101 int ndots = Integer.parseInt(token);
102 if (ndots >= 0) {
103 if (Options.check("verbose"))
104 System.out.println("setting ndots " + token);
105 return ndots;
106 }
107 }
108 catch (NumberFormatException e) {
109 }
110 return -1;
111 }
112
113 private void
configureFromLists(List lserver, List lsearch)114 configureFromLists(List lserver, List lsearch) {
115 if (servers == null && lserver.size() > 0)
116 servers = (String []) lserver.toArray(new String[0]);
117 if (searchlist == null && lsearch.size() > 0)
118 searchlist = (Name []) lsearch.toArray(new Name[0]);
119 }
120
121 private void
configureNdots(int lndots)122 configureNdots(int lndots) {
123 if (ndots < 0 && lndots > 0)
124 ndots = lndots;
125 }
126
127 /**
128 * Looks in the system properties to find servers and a search path.
129 * Servers are defined by dns.server=server1,server2...
130 * The search path is defined by dns.search=domain1,domain2...
131 */
132 private boolean
findProperty()133 findProperty() {
134 String prop;
135 List lserver = new ArrayList(0);
136 List lsearch = new ArrayList(0);
137 StringTokenizer st;
138
139 prop = System.getProperty("dns.server");
140 if (prop != null) {
141 st = new StringTokenizer(prop, ",");
142 while (st.hasMoreTokens())
143 addServer(st.nextToken(), lserver);
144 }
145
146 prop = System.getProperty("dns.search");
147 if (prop != null) {
148 st = new StringTokenizer(prop, ",");
149 while (st.hasMoreTokens())
150 addSearch(st.nextToken(), lsearch);
151 }
152 configureFromLists(lserver, lsearch);
153 return (servers != null && searchlist != null);
154 }
155
156 /**
157 * Uses the undocumented Sun DNS implementation to determine the configuration.
158 * This doesn't work or even compile with all JVMs (gcj, for example).
159 */
160 private boolean
findSunJVM()161 findSunJVM() {
162 List lserver = new ArrayList(0);
163 List lserver_tmp;
164 List lsearch = new ArrayList(0);
165 List lsearch_tmp;
166
167 try {
168 Class [] noClasses = new Class[0];
169 Object [] noObjects = new Object[0];
170 String resConfName = "sun.net.dns.ResolverConfiguration";
171 Class resConfClass = Class.forName(resConfName);
172 Object resConf;
173
174 // ResolverConfiguration resConf = ResolverConfiguration.open();
175 Method open = resConfClass.getDeclaredMethod("open", noClasses);
176 resConf = open.invoke(null, noObjects);
177
178 // lserver_tmp = resConf.nameservers();
179 Method nameservers = resConfClass.getMethod("nameservers",
180 noClasses);
181 lserver_tmp = (List) nameservers.invoke(resConf, noObjects);
182
183 // lsearch_tmp = resConf.searchlist();
184 Method searchlist = resConfClass.getMethod("searchlist",
185 noClasses);
186 lsearch_tmp = (List) searchlist.invoke(resConf, noObjects);
187 }
188 catch (Exception e) {
189 return false;
190 }
191
192 if (lserver_tmp.size() == 0)
193 return false;
194
195 if (lserver_tmp.size() > 0) {
196 Iterator it = lserver_tmp.iterator();
197 while (it.hasNext())
198 addServer((String) it.next(), lserver);
199 }
200
201 if (lsearch_tmp.size() > 0) {
202 Iterator it = lsearch_tmp.iterator();
203 while (it.hasNext())
204 addSearch((String) it.next(), lsearch);
205 }
206 configureFromLists(lserver, lsearch);
207 return true;
208 }
209
210 /**
211 * Looks in /etc/resolv.conf to find servers and a search path.
212 * "nameserver" lines specify servers. "domain" and "search" lines
213 * define the search path.
214 */
215 private void
findResolvConf(String file)216 findResolvConf(String file) {
217 InputStream in = null;
218 try {
219 in = new FileInputStream(file);
220 }
221 catch (FileNotFoundException e) {
222 return;
223 }
224 InputStreamReader isr = new InputStreamReader(in);
225 BufferedReader br = new BufferedReader(isr);
226 List lserver = new ArrayList(0);
227 List lsearch = new ArrayList(0);
228 int lndots = -1;
229 try {
230 String line;
231 while ((line = br.readLine()) != null) {
232 if (line.startsWith("nameserver")) {
233 StringTokenizer st = new StringTokenizer(line);
234 st.nextToken(); /* skip nameserver */
235 addServer(st.nextToken(), lserver);
236 }
237 else if (line.startsWith("domain")) {
238 StringTokenizer st = new StringTokenizer(line);
239 st.nextToken(); /* skip domain */
240 if (!st.hasMoreTokens())
241 continue;
242 if (lsearch.isEmpty())
243 addSearch(st.nextToken(), lsearch);
244 }
245 else if (line.startsWith("search")) {
246 if (!lsearch.isEmpty())
247 lsearch.clear();
248 StringTokenizer st = new StringTokenizer(line);
249 st.nextToken(); /* skip search */
250 while (st.hasMoreTokens())
251 addSearch(st.nextToken(), lsearch);
252 }
253 else if(line.startsWith("options")) {
254 StringTokenizer st = new StringTokenizer(line);
255 st.nextToken(); /* skip options */
256 while (st.hasMoreTokens()) {
257 String token = st.nextToken();
258 if (token.startsWith("ndots:")) {
259 lndots = parseNdots(token);
260 }
261 }
262 }
263 }
264 br.close();
265 }
266 catch (IOException e) {
267 }
268
269 configureFromLists(lserver, lsearch);
270 configureNdots(lndots);
271 }
272
273 private void
findUnix()274 findUnix() {
275 findResolvConf("/etc/resolv.conf");
276 }
277
278 private void
findNetware()279 findNetware() {
280 findResolvConf("sys:/etc/resolv.cfg");
281 }
282
283 /**
284 * Parses the output of winipcfg or ipconfig.
285 */
286 private void
findWin(InputStream in, Locale locale)287 findWin(InputStream in, Locale locale) {
288 String packageName = ResolverConfig.class.getPackage().getName();
289 String resPackageName = packageName + ".windows.DNSServer";
290 ResourceBundle res;
291 if (locale != null)
292 res = ResourceBundle.getBundle(resPackageName, locale);
293 else
294 res = ResourceBundle.getBundle(resPackageName);
295
296 String host_name = res.getString("host_name");
297 String primary_dns_suffix = res.getString("primary_dns_suffix");
298 String dns_suffix = res.getString("dns_suffix");
299 String dns_servers = res.getString("dns_servers");
300
301 BufferedReader br = new BufferedReader(new InputStreamReader(in));
302 try {
303 List lserver = new ArrayList();
304 List lsearch = new ArrayList();
305 String line = null;
306 boolean readingServers = false;
307 boolean readingSearches = false;
308 while ((line = br.readLine()) != null) {
309 StringTokenizer st = new StringTokenizer(line);
310 if (!st.hasMoreTokens()) {
311 readingServers = false;
312 readingSearches = false;
313 continue;
314 }
315 String s = st.nextToken();
316 if (line.indexOf(":") != -1) {
317 readingServers = false;
318 readingSearches = false;
319 }
320
321 if (line.indexOf(host_name) != -1) {
322 while (st.hasMoreTokens())
323 s = st.nextToken();
324 Name name;
325 try {
326 name = Name.fromString(s, null);
327 }
328 catch (TextParseException e) {
329 continue;
330 }
331 if (name.labels() == 1)
332 continue;
333 addSearch(s, lsearch);
334 } else if (line.indexOf(primary_dns_suffix) != -1) {
335 while (st.hasMoreTokens())
336 s = st.nextToken();
337 if (s.equals(":"))
338 continue;
339 addSearch(s, lsearch);
340 readingSearches = true;
341 } else if (readingSearches ||
342 line.indexOf(dns_suffix) != -1)
343 {
344 while (st.hasMoreTokens())
345 s = st.nextToken();
346 if (s.equals(":"))
347 continue;
348 addSearch(s, lsearch);
349 readingSearches = true;
350 } else if (readingServers ||
351 line.indexOf(dns_servers) != -1)
352 {
353 while (st.hasMoreTokens())
354 s = st.nextToken();
355 if (s.equals(":"))
356 continue;
357 addServer(s, lserver);
358 readingServers = true;
359 }
360 }
361
362 configureFromLists(lserver, lsearch);
363 }
364 catch (IOException e) {
365 }
366 return;
367 }
368
369 private void
findWin(InputStream in)370 findWin(InputStream in) {
371 String property = "org.xbill.DNS.windows.parse.buffer";
372 final int defaultBufSize = 8 * 1024;
373 int bufSize = Integer.getInteger(property, defaultBufSize).intValue();
374 BufferedInputStream b = new BufferedInputStream(in, bufSize);
375 b.mark(bufSize);
376 findWin(b, null);
377 if (servers == null) {
378 try {
379 b.reset();
380 }
381 catch (IOException e) {
382 return;
383 }
384 findWin(b, new Locale("", ""));
385 }
386 }
387
388 /**
389 * Calls winipcfg and parses the result to find servers and a search path.
390 */
391 private void
find95()392 find95() {
393 String s = "winipcfg.out";
394 try {
395 Process p;
396 p = Runtime.getRuntime().exec("winipcfg /all /batch " + s);
397 p.waitFor();
398 File f = new File(s);
399 findWin(new FileInputStream(f));
400 new File(s).delete();
401 }
402 catch (Exception e) {
403 return;
404 }
405 }
406
407 /**
408 * Calls ipconfig and parses the result to find servers and a search path.
409 */
410 private void
findNT()411 findNT() {
412 try {
413 Process p;
414 p = Runtime.getRuntime().exec("ipconfig /all");
415 findWin(p.getInputStream());
416 p.destroy();
417 }
418 catch (Exception e) {
419 return;
420 }
421 }
422
423 /**
424 * Parses the output of getprop, which is the only way to get DNS
425 * info on Android. getprop might disappear in future releases, so
426 * this code comes with a use-by date.
427 */
428 private void
findAndroid()429 findAndroid() {
430 // This originally looked for all lines containing .dns; but
431 // http://code.google.com/p/android/issues/detail?id=2207#c73
432 // indicates that net.dns* should always be the active nameservers, so
433 // we use those.
434 String re1 = "^\\d+(\\.\\d+){3}$";
435 String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$";
436 try {
437 ArrayList lserver = new ArrayList();
438 ArrayList lsearch = new ArrayList();
439 String line;
440 Process p = Runtime.getRuntime().exec("getprop");
441 InputStream in = p.getInputStream();
442 InputStreamReader isr = new InputStreamReader(in);
443 BufferedReader br = new BufferedReader(isr);
444 while ((line = br.readLine()) != null ) {
445 StringTokenizer t = new StringTokenizer(line, ":");
446 String name = t.nextToken();
447 if (name.indexOf( "net.dns" ) > -1) {
448 String v = t.nextToken();
449 v = v.replaceAll("[ \\[\\]]", "");
450 if ((v.matches(re1) || v.matches(re2)) &&
451 !lserver.contains(v))
452 lserver.add(v);
453 }
454 }
455 configureFromLists(lserver, lsearch);
456 } catch ( Exception e ) {
457 // ignore resolutely
458 }
459 }
460
461 /** Returns all located servers */
462 public String []
servers()463 servers() {
464 return servers;
465 }
466
467 /** Returns the first located server */
468 public String
server()469 server() {
470 if (servers == null)
471 return null;
472 return servers[0];
473 }
474
475 /** Returns all entries in the located search path */
476 public Name []
searchPath()477 searchPath() {
478 return searchlist;
479 }
480
481 /**
482 * Returns the located ndots value, or the default (1) if not configured.
483 * Note that ndots can only be configured in a resolv.conf file, and will only
484 * take effect if ResolverConfig uses resolv.conf directly (that is, if the
485 * JVM does not include the sun.net.dns.ResolverConfiguration class).
486 */
487 public int
ndots()488 ndots() {
489 if (ndots < 0)
490 return 1;
491 return ndots;
492 }
493
494 /** Gets the current configuration */
495 public static synchronized ResolverConfig
getCurrentConfig()496 getCurrentConfig() {
497 return currentConfig;
498 }
499
500 /** Gets the current configuration */
501 public static void
refresh()502 refresh() {
503 ResolverConfig newConfig = new ResolverConfig();
504 synchronized (ResolverConfig.class) {
505 currentConfig = newConfig;
506 }
507 }
508
509 }
510