001/*
002 * Copyright 2015-2024 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.dnsserverlookup;
012
013import org.minidns.util.PlatformDetection;
014
015import java.io.BufferedReader;
016import java.io.File;
017import java.io.FileInputStream;
018import java.io.IOException;
019import java.io.InputStreamReader;
020import java.nio.charset.StandardCharsets;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.logging.Level;
024import java.util.logging.Logger;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028public final class UnixUsingEtcResolvConf extends AbstractDnsServerLookupMechanism {
029
030    public static final DnsServerLookupMechanism INSTANCE = new UnixUsingEtcResolvConf();
031    public static final int PRIORITY = 2000;
032
033    private static final Logger LOGGER = Logger.getLogger(UnixUsingEtcResolvConf.class.getName());
034
035    private static final String RESOLV_CONF_FILE = "/etc/resolv.conf";
036    private static final Pattern NAMESERVER_PATTERN = Pattern.compile("^nameserver\\s+(.*)$");
037
038    private static List<String> cached;
039    private static long lastModified;
040
041    private UnixUsingEtcResolvConf() {
042        super(UnixUsingEtcResolvConf.class.getSimpleName(), PRIORITY);
043    }
044
045    @Override
046    public List<String> getDnsServerAddresses() {
047        File file = new File(RESOLV_CONF_FILE);
048        if (!file.exists()) {
049            // Not very unixoid systems
050            return null;
051        }
052
053        long currentLastModified = file.lastModified();
054        if (currentLastModified == lastModified && cached != null) {
055            return cached;
056        }
057
058        List<String> servers = new ArrayList<>();
059        BufferedReader reader = null;
060        try {
061            reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
062            String line;
063            while ((line = reader.readLine()) != null) {
064                Matcher matcher = NAMESERVER_PATTERN.matcher(line);
065                if (matcher.matches()) {
066                    servers.add(matcher.group(1).trim());
067                }
068            }
069        } catch (IOException e) {
070            LOGGER.log(Level.WARNING, "Could not read from " + RESOLV_CONF_FILE, e);
071            return null;
072        } finally {
073            if (reader != null) try {
074                reader.close();
075            } catch (IOException e) {
076                LOGGER.log(Level.WARNING, "Could not close reader", e);
077            }
078        }
079
080        if (servers.isEmpty()) {
081            LOGGER.fine("Could not find any nameservers in " + RESOLV_CONF_FILE);
082            return null;
083        }
084
085        cached = servers;
086        lastModified = currentLastModified;
087
088        return cached;
089    }
090
091    @Override
092    public boolean isAvailable() {
093        if (PlatformDetection.isAndroid()) {
094            // Don't rely on resolv.conf when on Android
095            return false;
096        }
097
098        File file = new File(RESOLV_CONF_FILE);
099
100        boolean resolvConfFileExists;
101        try {
102            resolvConfFileExists = file.exists();
103        } catch (SecurityException securityException) {
104            LOGGER.log(Level.FINE, "Access to /etc/resolv.conf not possible", securityException);
105            return false;
106        }
107        return resolvConfFileExists;
108    }
109
110}