001/**
002 *
003 * Copyright 2013-2019 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.util.dns.javax;
018
019import java.net.InetAddress;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.logging.Level;
023
024import javax.naming.NameNotFoundException;
025import javax.naming.NamingEnumeration;
026import javax.naming.NamingException;
027import javax.naming.directory.Attribute;
028import javax.naming.directory.Attributes;
029import javax.naming.directory.DirContext;
030import javax.naming.directory.InitialDirContext;
031
032import org.jivesoftware.smack.ConnectionConfiguration.DnssecMode;
033import org.jivesoftware.smack.initializer.SmackInitializer;
034import org.jivesoftware.smack.util.DNSUtil;
035import org.jivesoftware.smack.util.dns.DNSResolver;
036import org.jivesoftware.smack.util.dns.HostAddress;
037import org.jivesoftware.smack.util.dns.SRVRecord;
038
039import org.minidns.dnsname.DnsName;
040
041/**
042 * A DNS resolver (mostly for SRV records), which makes use of the API provided in the javax.* namespace.
043 *
044 * @author Florian Schmaus
045 *
046 */
047public class JavaxResolver extends DNSResolver implements SmackInitializer {
048
049    private static JavaxResolver instance;
050    private static DirContext dirContext;
051
052    static {
053        try {
054            dirContext = new InitialDirContext();
055        } catch (NamingException e) {
056            LOGGER.log(Level.SEVERE, "Could not construct InitialDirContext", e);
057        }
058
059        // Try to set this DNS resolver as primary one
060        setup();
061    }
062
063    public static synchronized DNSResolver getInstance() {
064        if (instance == null && isSupported()) {
065            instance = new JavaxResolver();
066        }
067        return instance;
068    }
069
070    public static boolean isSupported() {
071        return dirContext != null;
072    }
073
074    public static void setup() {
075        DNSUtil.setDNSResolver(getInstance());
076    }
077
078    public JavaxResolver() {
079         super(false);
080    }
081
082    @Override
083    protected List<SRVRecord> lookupSRVRecords0(DnsName name, List<HostAddress> failedAddresses, DnssecMode dnssecMode) {
084        List<SRVRecord> res = null;
085
086        Attribute srvAttribute;
087        try {
088            Attributes dnsLookup = dirContext.getAttributes(name.ace, new String[] { "SRV" });
089            srvAttribute = dnsLookup.get("SRV");
090            if (srvAttribute == null)
091               return null;
092        } catch (NameNotFoundException e) {
093            LOGGER.log(Level.FINEST, "No DNS SRV RR found for " + name, e);
094            return null;
095        } catch (NamingException e) {
096            LOGGER.log(Level.WARNING, "Exception while resolving DNS SRV RR for " + name, e);
097            return null;
098        }
099
100        try {
101            @SuppressWarnings("unchecked")
102            NamingEnumeration<String> srvRecords = (NamingEnumeration<String>) srvAttribute.getAll();
103            res = new ArrayList<>();
104            while (srvRecords.hasMore()) {
105                String srvRecordString = srvRecords.next();
106                String[] srvRecordEntries = srvRecordString.split(" ");
107                int priority = Integer.parseInt(srvRecordEntries[srvRecordEntries.length - 4]);
108                int port = Integer.parseInt(srvRecordEntries[srvRecordEntries.length - 2]);
109                int weight = Integer.parseInt(srvRecordEntries[srvRecordEntries.length - 3]);
110                String srvTarget = srvRecordEntries[srvRecordEntries.length - 1];
111                // Strip trailing '.' from srvTarget.
112                // Later MiniDNS version may do the right thing when DnsName.from() is called with a DNS name string
113                // having a trailing dot, so this can possibly be removed in future Smack versions.
114                if (srvTarget.length() > 0 && srvTarget.charAt(srvTarget.length() - 1) == '.') {
115                    srvTarget = srvTarget.substring(0, srvTarget.length() - 1);
116                }
117                DnsName host = DnsName.from(srvTarget);
118
119                List<InetAddress> hostAddresses = lookupHostAddress0(host, failedAddresses, dnssecMode);
120                if (shouldContinue(name, host, hostAddresses)) {
121                    continue;
122                }
123
124                SRVRecord srvRecord = new SRVRecord(host, port, priority, weight, hostAddresses);
125                res.add(srvRecord);
126            }
127        }
128        catch (NamingException e) {
129            LOGGER.log(Level.SEVERE, "Exception while resolving DNS SRV RR for" + name, e);
130        }
131
132        return res;
133    }
134
135    @Override
136    public List<Exception> initialize() {
137        setup();
138        return null;
139    }
140
141}