1// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
2
3package org.xbill.DNS;
4
5import java.io.*;
6import java.lang.reflect.*;
7import 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
34public class ResolverConfig {
35
36private String [] servers = null;
37private Name [] searchlist = null;
38private int ndots = -1;
39
40private static ResolverConfig currentConfig;
41
42static {
43	refresh();
44}
45
46public
47ResolverConfig() {
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
72private void
73addServer(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
81private void
82addSearch(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
97private int
98parseNdots(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
113private void
114configureFromLists(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
121private void
122configureNdots(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 */
132private boolean
133findProperty() {
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 */
160private boolean
161findSunJVM() {
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 */
215private void
216findResolvConf(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
273private void
274findUnix() {
275	findResolvConf("/etc/resolv.conf");
276}
277
278private void
279findNetware() {
280	findResolvConf("sys:/etc/resolv.cfg");
281}
282
283/**
284 * Parses the output of winipcfg or ipconfig.
285 */
286private void
287findWin(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
369private void
370findWin(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 */
391private void
392find95() {
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 */
410private void
411findNT() {
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 */
428private void
429findAndroid() {
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 */
462public String []
463servers() {
464	return servers;
465}
466
467/** Returns the first located server */
468public String
469server() {
470	if (servers == null)
471		return null;
472	return servers[0];
473}
474
475/** Returns all entries in the located search path */
476public Name []
477searchPath() {
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 */
487public int
488ndots() {
489	if (ndots < 0)
490		return 1;
491	return ndots;
492}
493
494/** Gets the current configuration */
495public static synchronized ResolverConfig
496getCurrentConfig() {
497	return currentConfig;
498}
499
500/** Gets the current configuration */
501public static void
502refresh() {
503	ResolverConfig newConfig = new ResolverConfig();
504	synchronized (ResolverConfig.class) {
505		currentConfig = newConfig;
506	}
507}
508
509}
510