1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.james.dnsserver;
23
24 import org.apache.avalon.framework.activity.Initializable;
25 import org.apache.avalon.framework.configuration.Configurable;
26 import org.apache.avalon.framework.configuration.Configuration;
27 import org.apache.avalon.framework.configuration.ConfigurationException;
28 import org.apache.avalon.framework.logger.AbstractLogEnabled;
29 import org.apache.james.api.dnsservice.TemporaryResolutionException;
30 import org.xbill.DNS.ARecord;
31 import org.xbill.DNS.Cache;
32 import org.xbill.DNS.Credibility;
33 import org.xbill.DNS.DClass;
34 import org.xbill.DNS.ExtendedResolver;
35 import org.xbill.DNS.Lookup;
36 import org.xbill.DNS.MXRecord;
37 import org.xbill.DNS.Name;
38 import org.xbill.DNS.PTRRecord;
39 import org.xbill.DNS.Record;
40 import org.xbill.DNS.Resolver;
41 import org.xbill.DNS.ResolverConfig;
42 import org.xbill.DNS.ReverseMap;
43 import org.xbill.DNS.TXTRecord;
44 import org.xbill.DNS.TextParseException;
45 import org.xbill.DNS.Type;
46
47 import java.net.InetAddress;
48 import java.net.UnknownHostException;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.Collection;
52 import java.util.Collections;
53 import java.util.Comparator;
54 import java.util.Iterator;
55 import java.util.List;
56 import java.util.Random;
57
58
59
60
61
62 public class DNSServer
63 extends AbstractLogEnabled
64 implements Configurable, Initializable, org.apache.james.api.dnsservice.DNSService, DNSServerMBean {
65
66
67
68
69
70 protected Resolver resolver;
71
72
73
74
75
76 protected Cache cache;
77
78
79
80
81
82 private int maxCacheSize = 50000;
83
84
85
86
87 private int dnsCredibility;
88
89
90
91
92 private List dnsServers = new ArrayList();
93
94
95
96
97 private Name[] searchPaths = null;
98
99
100
101
102 private Comparator mxComparator = new MXRecordComparator();
103
104
105
106
107
108 private boolean singleIPPerMX;
109
110
111
112
113
114 private boolean setAsDNSJavaDefault;
115
116 private String localHostName;
117
118 private String localCanonicalHostName;
119
120 private String localAddress;
121
122
123
124
125
126 public void configure( final Configuration configuration )
127 throws ConfigurationException {
128
129 final boolean autodiscover =
130 configuration.getChild( "autodiscover" ).getValueAsBoolean( true );
131
132 List sPaths = new ArrayList();
133 if (autodiscover) {
134 getLogger().info("Autodiscovery is enabled - trying to discover your system's DNS Servers");
135 String[] serversArray = ResolverConfig.getCurrentConfig().servers();
136 if (serversArray != null) {
137 for ( int i = 0; i < serversArray.length; i++ ) {
138 dnsServers.add(serversArray[ i ]);
139 getLogger().info("Adding autodiscovered server " + serversArray[i]);
140 }
141 }
142 Name[] systemSearchPath = ResolverConfig.getCurrentConfig().searchPath();
143 if (systemSearchPath != null && systemSearchPath.length > 0) {
144 sPaths.addAll(Arrays.asList(systemSearchPath));
145 }
146 if (getLogger().isInfoEnabled()) {
147 for (Iterator i = sPaths.iterator(); i.hasNext();) {
148 Name searchPath = (Name) i.next();
149 getLogger().info("Adding autodiscovered search path " + searchPath.toString());
150 }
151 }
152 }
153
154 singleIPPerMX = configuration.getChild( "singleIPperMX" ).getValueAsBoolean( false );
155
156 setAsDNSJavaDefault = configuration.getChild( "setAsDNSJavaDefault" ).getValueAsBoolean( true );
157
158
159 final Configuration serversConfiguration = configuration.getChild( "servers" );
160 final Configuration[] serverConfigurations =
161 serversConfiguration.getChildren( "server" );
162
163 for ( int i = 0; i < serverConfigurations.length; i++ ) {
164 dnsServers.add( serverConfigurations[ i ].getValue() );
165 }
166
167
168 final Configuration searchPathsConfiguration = configuration.getChild( "searchpaths" );
169 final Configuration[] searchPathsConfigurations =
170 searchPathsConfiguration.getChildren( "searchpath" );
171
172 for ( int i = 0; i < searchPathsConfigurations.length; i++ ) {
173 try {
174 sPaths.add( Name.fromString(searchPathsConfigurations[ i ].getValue()) );
175 } catch (TextParseException e) {
176 throw new ConfigurationException("Unable to parse searchpath host: "+searchPathsConfigurations[ i ].getValue(),e);
177 }
178 }
179
180 searchPaths = (Name[]) sPaths.toArray(new Name[0]);
181
182 if (dnsServers.isEmpty()) {
183 getLogger().info("No DNS servers have been specified or found by autodiscovery - adding 127.0.0.1");
184 dnsServers.add("127.0.0.1");
185 }
186
187 final boolean authoritative =
188 configuration.getChild( "authoritative" ).getValueAsBoolean( false );
189
190
191 dnsCredibility = authoritative ? Credibility.AUTH_ANSWER : Credibility.NONAUTH_ANSWER;
192
193 maxCacheSize = (int) configuration.getChild( "maxcachesize" ).getValueAsLong( maxCacheSize );
194 }
195
196
197
198
199 public void initialize()
200 throws Exception {
201
202 getLogger().debug("DNSService init...");
203
204
205 if (dnsServers.isEmpty()) {
206 try {
207 dnsServers.add( InetAddress.getLocalHost().getHostName() );
208 } catch ( UnknownHostException ue ) {
209 dnsServers.add( "127.0.0.1" );
210 }
211 }
212
213
214 final String[] serversArray = (String[])dnsServers.toArray(new String[0]);
215
216 if (getLogger().isInfoEnabled()) {
217 for(int c = 0; c < serversArray.length; c++) {
218 getLogger().info("DNS Server is: " + serversArray[c]);
219 }
220 }
221
222 try {
223 resolver = new ExtendedResolver( serversArray );
224 } catch (UnknownHostException uhe) {
225 getLogger().fatalError("DNS service could not be initialized. The DNS servers specified are not recognized hosts.", uhe);
226 throw uhe;
227 }
228
229 cache = new Cache (DClass.IN);
230 cache.setMaxEntries(maxCacheSize);
231
232 if (setAsDNSJavaDefault) {
233 Lookup.setDefaultResolver(resolver);
234 Lookup.setDefaultCache(cache, DClass.IN);
235 Lookup.setDefaultSearchPath(searchPaths);
236 getLogger().info("Registered cache, resolver and search paths as DNSJava defaults");
237 }
238
239
240
241
242
243 InetAddress addr = getLocalHost();
244 localCanonicalHostName = addr.getCanonicalHostName();
245 localHostName = addr.getHostName();
246 localAddress = addr.getHostAddress();
247
248 getLogger().debug("DNSService ...init end");
249 }
250
251
252
253
254
255
256 public String[] getDNSServers() {
257 return (String[])dnsServers.toArray(new String[0]);
258 }
259
260
261
262
263
264
265 public Name[] getSearchPaths() {
266 return searchPaths;
267 }
268
269
270
271
272
273
274
275
276
277
278
279 private List findMXRecordsRaw(String hostname) throws TemporaryResolutionException {
280 Record answers[] = lookup(hostname, Type.MX, "MX");
281 List servers = new ArrayList();
282 if (answers == null) {
283 return servers;
284 }
285
286 MXRecord mxAnswers[] = new MXRecord[answers.length];
287 for (int i = 0; i < answers.length; i++) {
288 mxAnswers[i] = (MXRecord)answers[i];
289 }
290
291 Arrays.sort(mxAnswers, mxComparator);
292
293 for (int i = 0; i < mxAnswers.length; i++) {
294 servers.add(mxAnswers[i].getTarget ().toString ());
295 getLogger().debug(new StringBuffer("Found MX record ").append(mxAnswers[i].getTarget ().toString ()).toString());
296 }
297 return servers;
298 }
299
300
301
302
303 public Collection findMXRecords(String hostname) throws TemporaryResolutionException {
304 List servers = new ArrayList();
305 try {
306 servers = findMXRecordsRaw(hostname);
307 return Collections.unmodifiableCollection(servers);
308 } finally {
309
310
311 if (servers.size () == 0) {
312 StringBuffer logBuffer =
313 new StringBuffer(128)
314 .append("Couldn't resolve MX records for domain ")
315 .append(hostname)
316 .append(".");
317 getLogger().info(logBuffer.toString());
318 try {
319 getByName(hostname);
320 servers.add(hostname);
321 } catch (UnknownHostException uhe) {
322
323
324
325 logBuffer = new StringBuffer(128)
326 .append("Couldn't resolve IP address for host ")
327 .append(hostname)
328 .append(".");
329 getLogger().error(logBuffer.toString());
330 }
331 }
332 }
333 }
334
335
336
337
338
339
340
341
342
343
344
345 protected Record[] lookup(String namestr, int type, String typeDesc) throws TemporaryResolutionException {
346
347 try {
348
349 Lookup l = new Lookup(namestr, type);
350
351 l.setCache(cache);
352 l.setResolver(resolver);
353 l.setCredibility(dnsCredibility);
354 l.setSearchPath(searchPaths);
355 Record[] r = l.run();
356
357 try {
358 if (l.getResult() == Lookup.TRY_AGAIN) {
359 throw new TemporaryResolutionException(
360 "DNSService is temporary not reachable");
361 } else {
362 return r;
363 }
364 } catch (IllegalStateException ise) {
365
366
367 getLogger().debug("Error determining result ", ise);
368 throw new TemporaryResolutionException(
369 "DNSService is temporary not reachable");
370 }
371
372
373 } catch (TextParseException tpe) {
374
375 getLogger().error("Couldn't parse name " + namestr, tpe);
376 return null;
377 }
378 }
379
380 protected Record[] lookupNoException(String namestr, int type, String typeDesc) {
381 try {
382 return lookup(namestr, type, typeDesc);
383 } catch (TemporaryResolutionException e) {
384 return null;
385 }
386 }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 private static class MXRecordComparator implements Comparator {
406 private final static Random random = new Random();
407 public int compare (Object a, Object b) {
408 int pa = ((MXRecord)a).getPriority();
409 int pb = ((MXRecord)b).getPriority();
410 return (pa == pb) ? (512 - random.nextInt(1024)) : pa - pb;
411 }
412 }
413
414
415
416
417 public Iterator getSMTPHostAddresses(final String domainName) throws TemporaryResolutionException {
418 return new Iterator() {
419 private Iterator mxHosts = findMXRecords(domainName).iterator();
420 private Iterator addresses = null;
421
422 public boolean hasNext() {
423
424
425
426
427
428
429
430 if ((addresses == null || !addresses.hasNext()) && mxHosts.hasNext()) do {
431 final String nextHostname = (String)mxHosts.next();
432 InetAddress[] addrs = null;
433 try {
434 if (singleIPPerMX) {
435 addrs = new InetAddress[] {getByName(nextHostname)};
436 } else {
437 addrs = getAllByName(nextHostname);
438 }
439 } catch (UnknownHostException uhe) {
440
441
442
443 StringBuffer logBuffer = new StringBuffer(128)
444 .append("Couldn't resolve IP address for discovered host ")
445 .append(nextHostname)
446 .append(".");
447 getLogger().error(logBuffer.toString());
448 }
449 final InetAddress[] ipAddresses = addrs;
450
451 addresses = new Iterator() {
452 int i = 0;
453
454 public boolean hasNext() {
455 return ipAddresses != null && i < ipAddresses.length;
456 }
457
458 public Object next() {
459 return new org.apache.mailet.HostAddress(nextHostname, "smtp://" + ipAddresses[i++].getHostAddress());
460 }
461
462 public void remove() {
463 throw new UnsupportedOperationException ("remove not supported by this iterator");
464 }
465 };
466 } while (!addresses.hasNext() && mxHosts.hasNext());
467
468 return addresses != null && addresses.hasNext();
469 }
470
471 public Object next() {
472 return addresses != null ? addresses.next() : null;
473 }
474
475 public void remove() {
476 throw new UnsupportedOperationException ("remove not supported by this iterator");
477 }
478 };
479 }
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497 private static String allowIPLiteral(String host) {
498 if ((host.charAt(host.length() - 1) == '.')) {
499 String possible_ip_literal = host.substring(0, host.length() - 1);
500 if (org.xbill.DNS.Address.isDottedQuad(possible_ip_literal)) {
501 host = possible_ip_literal;
502 }
503 }
504 return host;
505 }
506
507
508
509
510 public InetAddress getByName(String host) throws UnknownHostException {
511 String name = allowIPLiteral(host);
512
513 try {
514
515 if (name.equalsIgnoreCase(localHostName) || name.equalsIgnoreCase(localCanonicalHostName) ||name.equals(localAddress)) {
516 return getLocalHost();
517 }
518
519 return org.xbill.DNS.Address.getByAddress(name);
520 } catch (UnknownHostException e) {
521 Record[] records = lookupNoException(name, Type.A, "A");
522
523 if (records != null && records.length >= 1) {
524 ARecord a = (ARecord) records[0];
525 return InetAddress.getByAddress(name, a.getAddress().getAddress());
526 } else throw e;
527 }
528 }
529
530
531
532
533 public InetAddress[] getAllByName(String host) throws UnknownHostException {
534 String name = allowIPLiteral(host);
535 try {
536
537 if (name.equalsIgnoreCase(localHostName) || name.equalsIgnoreCase(localCanonicalHostName) ||name.equals(localAddress)) {
538 return new InetAddress[] {getLocalHost()};
539 }
540
541 InetAddress addr = org.xbill.DNS.Address.getByAddress(name);
542 return new InetAddress[] {addr};
543 } catch (UnknownHostException e) {
544 Record[] records = lookupNoException(name, Type.A, "A");
545
546 if (records != null && records.length >= 1) {
547 InetAddress [] addrs = new InetAddress[records.length];
548 for (int i = 0; i < records.length; i++) {
549 ARecord a = (ARecord) records[i];
550 addrs[i] = InetAddress.getByAddress(name, a.getAddress().getAddress());
551 }
552 return addrs;
553 } else throw e;
554 }
555 }
556
557
558
559
560 public Collection findTXTRecords(String hostname){
561 List txtR = new ArrayList();
562 Record[] records = lookupNoException(hostname, Type.TXT, "TXT");
563
564 if (records != null) {
565 for (int i = 0; i < records.length; i++) {
566 TXTRecord txt = (TXTRecord) records[i];
567 txtR.add(txt.rdataToString());
568 }
569
570 }
571 return txtR;
572 }
573
574
575
576
577 public String getHostName(InetAddress addr){
578 String result = null;
579 Name name = ReverseMap.fromAddress(addr);
580 Record[] records = lookupNoException(name.toString(), Type.PTR, "PTR");
581
582 if (records == null) {
583 result = addr.getHostAddress();
584 } else {
585 PTRRecord ptr = (PTRRecord) records[0];
586 result = ptr.getTarget().toString();
587 }
588 return result;
589 }
590
591
592
593
594 public InetAddress getLocalHost() throws UnknownHostException {
595 return InetAddress.getLocalHost();
596 }
597
598 }