1 // Copyright (c) 1999-2004 Brian Wellington (bwelling (at) 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 (at) meudal.net">Yannick Meudal</a> 31 * @author <a href="mailto:arnt (at) 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 { 43 refresh(); 44 } 45 46 public 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 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 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 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 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 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 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 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 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 274 findUnix() { 275 findResolvConf("/etc/resolv.conf"); 276 } 277 278 private void 279 findNetware() { 280 findResolvConf("sys:/etc/resolv.cfg"); 281 } 282 283 /** 284 * Parses the output of winipcfg or ipconfig. 285 */ 286 private void 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 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 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 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 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 [] 463 servers() { 464 return servers; 465 } 466 467 /** Returns the first located server */ 468 public String 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 [] 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 488 ndots() { 489 if (ndots < 0) 490 return 1; 491 return ndots; 492 } 493 494 /** Gets the current configuration */ 495 public static synchronized ResolverConfig 496 getCurrentConfig() { 497 return currentConfig; 498 } 499 500 /** Gets the current configuration */ 501 public static void 502 refresh() { 503 ResolverConfig newConfig = new ResolverConfig(); 504 synchronized (ResolverConfig.class) { 505 currentConfig = newConfig; 506 } 507 } 508 509 } 510