PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/james-2.2.0/src/java/org/apache/james/dnsserver/DNSServer.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 609 lines | 327 code | 67 blank | 215 comment | 67 complexity | 079441984fb7ccbc5d6b364f53955cc0 MD5 | raw file
  1. /***********************************************************************
  2. * Copyright (c) 2000-2004 The Apache Software Foundation. *
  3. * All rights reserved. *
  4. * ------------------------------------------------------------------- *
  5. * Licensed under the Apache License, Version 2.0 (the "License"); you *
  6. * may not use this file except in compliance with the License. You *
  7. * may obtain a copy of the License at: *
  8. * *
  9. * http://www.apache.org/licenses/LICENSE-2.0 *
  10. * *
  11. * Unless required by applicable law or agreed to in writing, software *
  12. * distributed under the License is distributed on an "AS IS" BASIS, *
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
  14. * implied. See the License for the specific language governing *
  15. * permissions and limitations under the License. *
  16. ***********************************************************************/
  17. package org.apache.james.dnsserver;
  18. import org.apache.avalon.framework.activity.Initializable;
  19. import org.apache.avalon.framework.activity.Disposable;
  20. import org.apache.avalon.framework.configuration.Configurable;
  21. import org.apache.avalon.framework.configuration.Configuration;
  22. import org.apache.avalon.framework.configuration.ConfigurationException;
  23. import org.apache.avalon.framework.logger.AbstractLogEnabled;
  24. import org.xbill.DNS.Cache;
  25. import org.xbill.DNS.Credibility;
  26. import org.xbill.DNS.DClass;
  27. import org.xbill.DNS.ExtendedResolver;
  28. import org.xbill.DNS.FindServer;
  29. import org.xbill.DNS.Lookup;
  30. import org.xbill.DNS.Message;
  31. import org.xbill.DNS.MXRecord;
  32. import org.xbill.DNS.ARecord;
  33. import org.xbill.DNS.Name;
  34. import org.xbill.DNS.Rcode;
  35. import org.xbill.DNS.Record;
  36. import org.xbill.DNS.Resolver;
  37. import org.xbill.DNS.RRset;
  38. import org.xbill.DNS.SetResponse;
  39. import org.xbill.DNS.TextParseException;
  40. import org.xbill.DNS.Type;
  41. import java.net.InetAddress;
  42. import java.net.UnknownHostException;
  43. import java.util.*;
  44. /**
  45. * Provides DNS client functionality to services running
  46. * inside James
  47. */
  48. public class DNSServer
  49. extends AbstractLogEnabled
  50. implements Configurable, Initializable, Disposable,
  51. org.apache.james.services.DNSServer, DNSServerMBean {
  52. /**
  53. * A resolver instance used to retrieve DNS records. This
  54. * is a reference to a third party library object.
  55. */
  56. private Resolver resolver;
  57. /**
  58. * A TTL cache of results received from the DNS server. This
  59. * is a reference to a third party library object.
  60. */
  61. private Cache cache;
  62. /**
  63. * Whether the DNS response is required to be authoritative
  64. */
  65. private int dnsCredibility;
  66. /**
  67. * The DNS servers to be used by this service
  68. */
  69. private List dnsServers = new ArrayList();
  70. /**
  71. * The MX Comparator used in the MX sort.
  72. */
  73. private Comparator mxComparator = new MXRecordComparator();
  74. /**
  75. * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
  76. */
  77. public void configure( final Configuration configuration )
  78. throws ConfigurationException {
  79. final boolean autodiscover =
  80. configuration.getChild( "autodiscover" ).getValueAsBoolean( true );
  81. if (autodiscover) {
  82. getLogger().info("Autodiscovery is enabled - trying to discover your system's DNS Servers");
  83. String[] serversArray = FindServer.servers();
  84. if (serversArray != null) {
  85. for ( int i = 0; i < serversArray.length; i++ ) {
  86. dnsServers.add(serversArray[ i ]);
  87. getLogger().info("Adding autodiscovered server " + serversArray[i]);
  88. }
  89. }
  90. }
  91. // Get the DNS servers that this service will use for lookups
  92. final Configuration serversConfiguration = configuration.getChild( "servers" );
  93. final Configuration[] serverConfigurations =
  94. serversConfiguration.getChildren( "server" );
  95. for ( int i = 0; i < serverConfigurations.length; i++ ) {
  96. dnsServers.add( serverConfigurations[ i ].getValue() );
  97. }
  98. if (dnsServers.isEmpty()) {
  99. getLogger().info("No DNS servers have been specified or found by autodiscovery - adding 127.0.0.1");
  100. dnsServers.add("127.0.0.1");
  101. }
  102. final boolean authoritative =
  103. configuration.getChild( "authoritative" ).getValueAsBoolean( false );
  104. // TODO: Check to see if the credibility field is being used correctly. From the
  105. // docs I don't think so
  106. dnsCredibility = authoritative ? Credibility.AUTH_ANSWER : Credibility.NONAUTH_ANSWER;
  107. }
  108. /**
  109. * @see org.apache.avalon.framework.activity.Initializable#initialize()
  110. */
  111. public void initialize()
  112. throws Exception {
  113. getLogger().debug("DNSServer init...");
  114. // If no DNS servers were configured, default to local host
  115. if (dnsServers.isEmpty()) {
  116. try {
  117. dnsServers.add( InetAddress.getLocalHost().getHostName() );
  118. } catch ( UnknownHostException ue ) {
  119. dnsServers.add( "127.0.0.1" );
  120. }
  121. }
  122. //Create the extended resolver...
  123. final String[] serversArray = (String[])dnsServers.toArray(new String[0]);
  124. if (getLogger().isInfoEnabled()) {
  125. for(int c = 0; c < serversArray.length; c++) {
  126. getLogger().info("DNS Server is: " + serversArray[c]);
  127. }
  128. }
  129. try {
  130. resolver = new ExtendedResolver( serversArray );
  131. Lookup.setDefaultResolver(resolver);
  132. } catch (UnknownHostException uhe) {
  133. getLogger().fatalError("DNS service could not be initialized. The DNS servers specified are not recognized hosts.", uhe);
  134. throw uhe;
  135. }
  136. cache = new Cache (DClass.IN);
  137. getLogger().debug("DNSServer ...init end");
  138. }
  139. /**
  140. * <p>Return the list of DNS servers in use by this service</p>
  141. *
  142. * @return an array of DNS server names
  143. */
  144. public String[] getDNSServers() {
  145. return (String[])dnsServers.toArray(new String[0]);
  146. }
  147. /**
  148. * <p>Return a prioritized unmodifiable list of MX records
  149. * obtained from the server.</p>
  150. *
  151. * @param hostname domain name to look up
  152. *
  153. * @return a unmodifiable list of MX records corresponding to
  154. * this mail domain name
  155. */
  156. public Collection findMXRecords(String hostname) {
  157. Record answers[] = lookup(hostname, Type.MX);
  158. List servers = new ArrayList();
  159. try {
  160. if (answers == null) {
  161. return servers;
  162. }
  163. MXRecord mxAnswers[] = new MXRecord[answers.length];
  164. for (int i = 0; i < answers.length; i++) {
  165. mxAnswers[i] = (MXRecord)answers[i];
  166. }
  167. Arrays.sort(mxAnswers, mxComparator);
  168. for (int i = 0; i < mxAnswers.length; i++) {
  169. servers.add(mxAnswers[i].getTarget ().toString ());
  170. getLogger().debug(new StringBuffer("Found MX record ").append(mxAnswers[i].getTarget ().toString ()).toString());
  171. }
  172. return Collections.unmodifiableCollection(servers);
  173. } finally {
  174. //If we found no results, we'll add the original domain name if
  175. //it's a valid DNS entry
  176. if (servers.size () == 0) {
  177. StringBuffer logBuffer =
  178. new StringBuffer(128)
  179. .append("Couldn't resolve MX records for domain ")
  180. .append(hostname)
  181. .append(".");
  182. getLogger().info(logBuffer.toString());
  183. try {
  184. getByName(hostname);
  185. servers.add(hostname);
  186. } catch (UnknownHostException uhe) {
  187. // The original domain name is not a valid host,
  188. // so we can't add it to the server list. In this
  189. // case we return an empty list of servers
  190. logBuffer = new StringBuffer(128)
  191. .append("Couldn't resolve IP address for host ")
  192. .append(hostname)
  193. .append(".");
  194. getLogger().error(logBuffer.toString());
  195. }
  196. }
  197. }
  198. }
  199. /**
  200. * Looks up DNS records of the specified type for the specified name.
  201. *
  202. * This method is a public wrapper for the private implementation
  203. * method
  204. *
  205. * @param name the name of the host to be looked up
  206. * @param type the type of record desired
  207. */
  208. public Record[] lookup(String name, int type) {
  209. return rawDNSLookup(name,false,type);
  210. }
  211. /**
  212. * Looks up DNS records of the specified type for the specified name
  213. *
  214. * @param namestr the name of the host to be looked up
  215. * @param querysent whether the query has already been sent to the DNS servers
  216. * @param type the type of record desired
  217. */
  218. private Record[] rawDNSLookup(String namestr, boolean querysent, int type) {
  219. Name name = null;
  220. try {
  221. name = Name.fromString(namestr, Name.root);
  222. } catch (TextParseException tpe) {
  223. // TODO: Figure out how to handle this correctly.
  224. getLogger().error("Couldn't parse name " + namestr, tpe);
  225. return null;
  226. }
  227. int dclass = DClass.IN;
  228. SetResponse cached = cache.lookupRecords(name, type, dnsCredibility);
  229. if (cached.isSuccessful()) {
  230. getLogger().debug(new StringBuffer(256)
  231. .append("Retrieving MX record for ")
  232. .append(name).append(" from cache")
  233. .toString());
  234. return processSetResponse(cached);
  235. }
  236. else if (cached.isNXDOMAIN() || cached.isNXRRSET()) {
  237. return null;
  238. }
  239. else if (querysent) {
  240. return null;
  241. }
  242. else {
  243. getLogger().debug(new StringBuffer(256)
  244. .append("Looking up MX record for ")
  245. .append(name)
  246. .toString());
  247. Record question = Record.newRecord(name, type, dclass);
  248. Message query = Message.newQuery(question);
  249. Message response = null;
  250. try {
  251. response = resolver.send(query);
  252. }
  253. catch (Exception ex) {
  254. getLogger().warn("Query error!", ex);
  255. return null;
  256. }
  257. int rcode = response.getHeader().getRcode();
  258. if (rcode == Rcode.NOERROR || rcode == Rcode.NXDOMAIN) {
  259. cached = cache.addMessage(response);
  260. if (cached != null && cached.isSuccessful()) {
  261. return processSetResponse(cached);
  262. }
  263. }
  264. if (rcode != Rcode.NOERROR) {
  265. return null;
  266. }
  267. return rawDNSLookup(namestr, true, type);
  268. }
  269. }
  270. private Record[] processSetResponse(SetResponse sr) {
  271. Record [] answers;
  272. int answerCount = 0, n = 0;
  273. RRset [] rrsets = sr.answers();
  274. answerCount = 0;
  275. for (int i = 0; i < rrsets.length; i++) {
  276. answerCount += rrsets[i].size();
  277. }
  278. answers = new Record[answerCount];
  279. for (int i = 0; i < rrsets.length; i++) {
  280. Iterator iter = rrsets[i].rrs();
  281. while (iter.hasNext()) {
  282. Record r = (Record)iter.next();
  283. answers[n++] = r;
  284. }
  285. }
  286. return answers;
  287. }
  288. /* RFC 2821 section 5 requires that we sort the MX records by their
  289. * preference, and introduce a randomization. This Comparator does
  290. * comparisons as normal unless the values are equal, in which case
  291. * it "tosses a coin", randomly speaking.
  292. *
  293. * This way MX record w/preference 0 appears before MX record
  294. * w/preference 1, but a bunch of MX records with the same preference
  295. * would appear in different orders each time.
  296. *
  297. * Reminder for maintainers: the return value on a Comparator can
  298. * be counter-intuitive for those who aren't used to the old C
  299. * strcmp function:
  300. *
  301. * < 0 ==> a < b
  302. * = 0 ==> a = b
  303. * > 0 ==> a > b
  304. */
  305. private static class MXRecordComparator implements Comparator {
  306. private final static Random random = new Random();
  307. public int compare (Object a, Object b) {
  308. int pa = ((MXRecord)a).getPriority();
  309. int pb = ((MXRecord)b).getPriority();
  310. return (pa == pb) ? (512 - random.nextInt(1024)) : pa - pb;
  311. }
  312. }
  313. /*
  314. * Returns an Iterator over org.apache.mailet.HostAddress, a
  315. * specialized subclass of javax.mail.URLName, which provides
  316. * location information for servers that are specified as mail
  317. * handlers for the given hostname. This is done using MX records,
  318. * and the HostAddress instances are returned sorted by MX priority.
  319. * If no host is found for domainName, the Iterator returned will be
  320. * empty and the first call to hasNext() will return false. The
  321. * Iterator is a nested iterator: the outer iteration is over the
  322. * results of the MX record lookup, and the inner iteration is over
  323. * potentially multiple A records for each MX record. DNS lookups
  324. * are deferred until actually needed.
  325. *
  326. * @since v2.2.0a16-unstable
  327. * @param domainName - the domain for which to find mail servers
  328. * @return an Iterator over HostAddress instances, sorted by priority
  329. */
  330. public Iterator getSMTPHostAddresses(final String domainName) {
  331. return new Iterator() {
  332. private Iterator mxHosts = findMXRecords(domainName).iterator();
  333. private Iterator addresses = null;
  334. public boolean hasNext() {
  335. /* Make sure that when next() is called, that we can
  336. * provide a HostAddress. This means that we need to
  337. * have an inner iterator, and verify that it has
  338. * addresses. We could, for example, run into a
  339. * situation where the next mxHost didn't have any valid
  340. * addresses.
  341. */
  342. if ((addresses == null || !addresses.hasNext()) && mxHosts.hasNext()) do {
  343. final String nextHostname = (String)mxHosts.next();
  344. InetAddress[] addrs = null;
  345. try {
  346. addrs = getAllByName(nextHostname);
  347. } catch (UnknownHostException uhe) {
  348. // this should never happen, since we just got
  349. // this host from mxHosts, which should have
  350. // already done this check.
  351. StringBuffer logBuffer = new StringBuffer(128)
  352. .append("Couldn't resolve IP address for discovered host ")
  353. .append(nextHostname)
  354. .append(".");
  355. getLogger().error(logBuffer.toString());
  356. }
  357. final InetAddress[] ipAddresses = addrs;
  358. addresses = new Iterator() {
  359. int i = 0;
  360. public boolean hasNext() {
  361. return ipAddresses != null && i < ipAddresses.length;
  362. }
  363. public Object next() {
  364. return new org.apache.mailet.HostAddress(nextHostname, "smtp://" + ipAddresses[i++].getHostAddress());
  365. }
  366. public void remove() {
  367. throw new UnsupportedOperationException ("remove not supported by this iterator");
  368. }
  369. };
  370. } while (!addresses.hasNext() && mxHosts.hasNext());
  371. return addresses != null && addresses.hasNext();
  372. }
  373. public Object next() {
  374. return addresses != null ? addresses.next() : null;
  375. }
  376. public void remove() {
  377. throw new UnsupportedOperationException ("remove not supported by this iterator");
  378. }
  379. };
  380. }
  381. /* java.net.InetAddress.get[All]ByName(String) allows an IP literal
  382. * to be passed, and will recognize it even with a trailing '.'.
  383. * However, org.xbill.DNS.Address does not recognize an IP literal
  384. * with a trailing '.' character. The problem is that when we
  385. * lookup an MX record for some domains, we may find an IP address,
  386. * which will have had the trailing '.' appended by the time we get
  387. * it back from dnsjava. An MX record is not allowed to have an IP
  388. * address as the right-hand-side, but there are still plenty of
  389. * such records on the Internet. Since java.net.InetAddress can
  390. * handle them, for the time being we've decided to support them.
  391. *
  392. * These methods are NOT intended for use outside of James, and are
  393. * NOT declared by the org.apache.james.services.DNSServer. This is
  394. * currently a stopgap measure to be revisited for the next release.
  395. */
  396. private static String allowIPLiteral(String host) {
  397. if ((host.charAt(host.length() - 1) == '.')) {
  398. String possible_ip_literal = host.substring(0, host.length() - 1);
  399. if (org.xbill.DNS.Address.isDottedQuad(possible_ip_literal)) {
  400. host = possible_ip_literal;
  401. }
  402. }
  403. return host;
  404. }
  405. /**
  406. * @see java.net.InetAddress#getByName(String)
  407. */
  408. public static InetAddress getByName(String host) throws UnknownHostException {
  409. return org.xbill.DNS.Address.getByName(allowIPLiteral(host));
  410. }
  411. /**
  412. * @see java.net.InetAddress#getByAllName(String)
  413. */
  414. public static InetAddress[] getAllByName(String host) throws UnknownHostException {
  415. return org.xbill.DNS.Address.getAllByName(allowIPLiteral(host));
  416. }
  417. /**
  418. * A way to get mail hosts to try. If any MX hosts are found for the
  419. * domain name with which this is constructed, then these MX hostnames
  420. * are returned in priority sorted order, lowest priority numbers coming
  421. * first. And, whenever multiple hosts have the same priority then these
  422. * are returned in a randomized order within that priority group, as
  423. * specified in RFC 2821, Section 5.
  424. *
  425. * If no MX hosts are found for the domain name, then a DNS search is
  426. * performed for an A record. If an A record is found then domainName itself
  427. * will be returned by the Iterator, and it will be the only object in
  428. * the Iterator. If however no A record is found (in addition to no MX
  429. * record) then the Iterator constructed will be empty; the first call to
  430. * its hasNext() will return false.
  431. *
  432. * This behavior attempts to satisfy the requirements of RFC 2821, Section 5.
  433. * @since v2.2.0a16-unstable
  434. */
  435. /**** THIS CODE IS BROKEN AND UNUSED ****/
  436. /* this code was used in getSMTPHostAddresses as:
  437. private Iterator mxHosts = new MxSorter(domainName);
  438. This class effectively replaces findMXRecords. If
  439. it is to be kept, it should replace the body of that
  440. method. The fixes would be to either implement a
  441. more robust DNS lookup, or to replace the Type.A
  442. lookup with InetAddress.getByName(), which is what
  443. findMXRecords uses. */
  444. private class MxSorter implements Iterator {
  445. private int priorListPriority = Integer.MIN_VALUE;
  446. private ArrayList equiPriorityList = new ArrayList();
  447. private Record[] mxRecords;
  448. private Random rnd = new Random ();
  449. /* The implementation of this class attempts to achieve efficiency by
  450. * performing no more sorting of the rawMxRecords than necessary. In the
  451. * large majority of cases the first attempt, made by a client of this class
  452. * to connect to an SMTP server for a given domain, will succeed. As such,
  453. * in most cases only one call will be made to this Iterator's
  454. * next(), and in that majority of cases there will have been no need
  455. * to sort the array of MX Records. This implementation would, however, be
  456. * relatively inefficient in the case where all hosts fail, when every
  457. * Object is called out of a long Iterator.
  458. */
  459. private MxSorter(String domainName) {
  460. mxRecords = lookup(domainName, Type.MX);
  461. if (mxRecords == null || mxRecords.length == 0) {
  462. //no MX records were found, so try to use the domainName
  463. Record[] aRecords = lookup(domainName, Type.A);
  464. if(aRecords != null && aRecords.length > 0) {
  465. equiPriorityList.add(domainName);
  466. }
  467. }
  468. }
  469. /**
  470. * Sets presentPriorityList to contain all hosts
  471. * which have the least priority greater than pastPriority.
  472. * When this is called, both (rawMxRecords.length > 0) and
  473. * (presentPriorityList.size() == 0), by contract.
  474. * In the case where this is called repeatedly, so that priorListPriority
  475. * has already become the highest of the priorities in the rawMxRecords,
  476. * then this returns without having added any elements to
  477. * presentPriorityList; presentPriorityList.size remains zero.
  478. */
  479. private void createPriorityList(){
  480. int leastPriorityFound = Integer.MAX_VALUE;
  481. /* We loop once through the rawMxRecords, finding the lowest priority
  482. * greater than priorListPriority, and collecting all the hostnames
  483. * with that priority into equiPriorityList.
  484. */
  485. for (int i = 0; i < mxRecords.length; i++) {
  486. MXRecord thisRecord = (MXRecord)mxRecords[i];
  487. int thisRecordPriority = thisRecord.getPriority();
  488. if (thisRecordPriority > priorListPriority) {
  489. if (thisRecordPriority < leastPriorityFound) {
  490. equiPriorityList.clear();
  491. leastPriorityFound = thisRecordPriority;
  492. equiPriorityList.add(thisRecord.getTarget().toString());
  493. } else if (thisRecordPriority == leastPriorityFound) {
  494. equiPriorityList.add(thisRecord.getTarget().toString());
  495. }
  496. }
  497. }
  498. priorListPriority = leastPriorityFound;
  499. }
  500. public boolean hasNext(){
  501. if (equiPriorityList.size() > 0){
  502. return true;
  503. }else if (mxRecords != null && mxRecords.length > 0){
  504. createPriorityList();
  505. return equiPriorityList.size() > 0;
  506. } else{
  507. return false;
  508. }
  509. }
  510. public Object next(){
  511. if (hasNext()){
  512. /* this randomization is done to comply with RFC-2821 */
  513. /* Note: java.util.Random.nextInt(limit) is about twice as fast as (int)(Math.random()*limit) */
  514. int getIndex = rnd.nextInt(equiPriorityList.size());
  515. Object returnElement = equiPriorityList.get(getIndex);
  516. equiPriorityList.remove(getIndex);
  517. return returnElement;
  518. }else{
  519. throw new NoSuchElementException();
  520. }
  521. }
  522. public void remove () {
  523. throw new UnsupportedOperationException ("remove not supported by this iterator");
  524. }
  525. }
  526. /**
  527. * The dispose operation is called at the end of a components lifecycle.
  528. * Instances of this class use this method to release and destroy any
  529. * resources that they own.
  530. *
  531. * This implementation shuts down org.xbill.DNS.Cache
  532. *
  533. * @throws Exception if an error is encountered during shutdown
  534. */
  535. public void dispose()
  536. {
  537. //setting the clean interval to a negative value, will terminate
  538. //the Cache cleaner thread.
  539. cache.setCleanInterval (-1);
  540. }
  541. }