net.spfbl.core.Reverse.java Source code

Java tutorial

Introduction

Here is the source code for net.spfbl.core.Reverse.java

Source

/*
 * This file is part of SPFBL.
 * 
 * SPFBL is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * SPFBL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with SPFBL.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.spfbl.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.naming.CommunicationException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.ServiceUnavailableException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import net.spfbl.spf.SPF;
import net.spfbl.whois.Domain;
import net.spfbl.whois.Subnet;
import net.spfbl.whois.SubnetIPv4;
import net.spfbl.whois.SubnetIPv6;
import org.apache.commons.lang3.SerializationUtils;

/**
 * Classe que representa o cache do IP reverso.
 * 
 * @author Leandro Carlos Rodrigues <leandro@spfbl.ne
 */
public final class Reverse implements Serializable {

    private static final long serialVersionUID = 1L;

    private final String ip;
    private TreeSet<String> addressSet = null;
    private int queryCount = 0;
    private long lastQuery;

    /**
     * Mapa de atributos da verificao do reverso.
     */
    private static final HashMap<String, Reverse> MAP = new HashMap<String, Reverse>();
    /**
     * Flag que indica se o cache foi modificado.
     */
    private static boolean CHANGED = false;
    /**
     * O prximo registro de reverso que deve ser atualizado.
     */
    private static Reverse refresh = null;

    private static synchronized Reverse dropExact(String ip) {
        Reverse ret = MAP.remove(ip);
        if (ret != null) {
            CHANGED = true;
        }
        return ret;
    }

    private static synchronized Reverse putExact(String key, Reverse value) {
        Reverse reverse = MAP.put(key, value);
        if (!value.equals(reverse)) {
            CHANGED = true;
        }
        return reverse;
    }

    private static synchronized TreeSet<String> keySet() {
        TreeSet<String> keySet = new TreeSet<String>();
        keySet.addAll(MAP.keySet());
        return keySet;
    }

    private static synchronized HashMap<String, Reverse> getMap() {
        HashMap<String, Reverse> map = new HashMap<String, Reverse>();
        map.putAll(MAP);
        return map;
    }

    private static Reverse getExact(String ip) {
        return MAP.get(ip);
    }

    private static synchronized Reverse getRefreshReverse() {
        Reverse reverse = refresh;
        refresh = null;
        return reverse;
    }

    private static synchronized void addQuery(Reverse reverse) {
        reverse.queryCount++;
        reverse.lastQuery = System.currentTimeMillis();
        if (refresh == null) {
            refresh = reverse;
        } else if (refresh.queryCount < reverse.queryCount) {
            refresh = reverse;
        }
        CHANGED = true;
    }

    private boolean contains(String host) {
        if (!Domain.isHostname(host)) {
            return false;
        } else if (addressSet == null) {
            return false;
        } else {
            host = Domain.normalizeHostname(host, true);
            return addressSet.contains(host);
        }
    }

    public TreeSet<String> getAddressSet(boolean refresh) {
        if (addressSet == null) {
            return new TreeSet<String>();
        } else {
            if (refresh) {
                refresh();
            }
            TreeSet<String> resultSet = new TreeSet<String>();
            resultSet.addAll(addressSet);
            return resultSet;
        }
    }

    public TreeSet<String> getAddressSet() {
        if (addressSet == null) {
            return new TreeSet<String>();
        } else {
            TreeSet<String> resultSet = new TreeSet<String>();
            resultSet.addAll(addressSet);
            return resultSet;
        }
    }

    public static TreeSet<String> getValidSet(String ip, boolean refresh) {
        Reverse reverse = Reverse.get(ip, refresh);
        return reverse.getAddressSet(ip, refresh);
    }

    public static TreeSet<String> getValidSet(String ip) {
        Reverse reverse = Reverse.get(ip);
        return reverse.getAddressSet(ip, false);
    }

    public static TreeSet<String> getSet(String ip, boolean refresh) {
        Reverse reverse = Reverse.get(ip);
        if (refresh) {
            reverse.refresh();
        }
        return reverse.getAddressSet();
    }

    public TreeSet<String> getAddressSet(String ip, boolean refresh) {
        if (addressSet == null) {
            return new TreeSet<String>();
        } else {
            if (refresh) {
                refresh();
            }
            TreeSet<String> resultSet = new TreeSet<String>();
            for (String hostname : addressSet) {
                if (SPF.matchHELO(ip, hostname, refresh)) {
                    resultSet.add(hostname);
                }
            }
            return resultSet;
        }
    }

    private int getQueryCount() {
        return queryCount;
    }

    private String getAddressOnly() {
        try {
            if (addressSet == null) {
                return null;
            } else if (addressSet.size() == 1) {
                return addressSet.first();
            } else {
                return null;
            }
        } catch (NoSuchElementException ex) {
            return null;
        }
    }

    private Reverse(String ip) {
        this.ip = Subnet.normalizeIP(ip);
        refresh();
        this.lastQuery = System.currentTimeMillis();
    }

    public static String getListedIP(String ip, String server, String... valueSet) {
        return getListedIP(ip, server, Arrays.asList(valueSet));
    }

    public static String getListedIP(String ip, String server, Collection<String> valueSet) {
        String host = Reverse.getHostReverse(ip, server);
        if (host == null) {
            return null;
        } else {
            try {
                TreeSet<String> IPv4Set = null;
                TreeSet<String> IPv6Set = null;
                for (String value : valueSet) {
                    if (SubnetIPv4.isValidIPv4(value)) {
                        if (IPv4Set == null) {
                            IPv4Set = getIPv4Set(host);
                        }
                        if (IPv4Set.contains(value)) {
                            return value;
                        }
                    } else if (SubnetIPv6.isValidIPv6(value)) {
                        if (IPv6Set == null) {
                            IPv6Set = getIPv6Set(host);
                        }
                        if (IPv6Set.contains(value)) {
                            return value;
                        }
                    }
                }
                return null;
            } catch (CommunicationException ex) {
                Server.logDebug("DNS service '" + server + "' unreachable.");
                return null;
            } catch (ServiceUnavailableException ex) {
                Server.logDebug("DNS service '" + server + "' unavailable.");
                return null;
            } catch (NameNotFoundException ex) {
                // No listado.
                return null;
            } catch (NamingException ex) {
                Server.logError(ex);
                return null;
            }
        }
    }

    public static String getListedHost(String host, String server, String... valueSet) {
        return getListedHost(host, server, Arrays.asList(valueSet));
    }

    public static String getListedHost(String host, String zone, Collection<String> valueSet) {
        host = Domain.normalizeHostname(host, false);
        if (host == null) {
            return null;
        } else {
            try {
                host = host + '.' + zone;
                TreeSet<String> IPv4Set = null;
                TreeSet<String> IPv6Set = null;
                for (String value : valueSet) {
                    if (SubnetIPv4.isValidIPv4(value)) {
                        if (IPv4Set == null) {
                            IPv4Set = getIPv4Set(host);
                        }
                        if (IPv4Set.contains(value)) {
                            return value;
                        }
                    } else if (SubnetIPv6.isValidIPv6(value)) {
                        if (IPv6Set == null) {
                            IPv6Set = getIPv6Set(host);
                        }
                        if (IPv6Set.contains(value)) {
                            return value;
                        }
                    }
                }
                return null;
            } catch (CommunicationException ex) {
                Server.logDebug("DNS service '" + zone + "' unreachable.");
                return null;
            } catch (ServiceUnavailableException ex) {
                Server.logDebug("DNS service '" + zone + "' unavailable.");
                return null;
            } catch (NameNotFoundException ex) {
                // No listado.
                return null;
            } catch (NamingException ex) {
                Server.logError(ex);
                return null;
            }
        }
    }

    public static boolean isListedIP(String ip, String zone, String value) {
        String host = Reverse.getHostReverse(ip, zone);
        if (host == null) {
            return false;
        } else {
            try {
                if (SubnetIPv4.isValidIPv4(value)) {
                    return getIPv4Set(host).contains(value);
                } else if (SubnetIPv6.isValidIPv6(value)) {
                    return getIPv6Set(host).contains(value);
                } else {
                    return false;
                }
            } catch (CommunicationException ex) {
                Server.logDebug("DNS service '" + zone + "' unreachable.");
                return false;
            } catch (ServiceUnavailableException ex) {
                Server.logDebug("DNS service '" + zone + "' unavailable.");
                return false;
            } catch (NameNotFoundException ex) {
                // No listado.
                return false;
            } catch (NamingException ex) {
                Server.logError(ex);
                return false;
            }
        }
    }

    public static boolean isListedHost(String host, String zone, String value) {
        host = Domain.normalizeHostname(host, true);
        if (host == null) {
            return false;
        } else {
            try {
                host = host + '.' + zone;
                if (SubnetIPv4.isValidIPv4(value)) {
                    return getIPv4Set(host).contains(value);
                } else if (SubnetIPv6.isValidIPv6(value)) {
                    return getIPv6Set(host).contains(value);
                } else {
                    return false;
                }
            } catch (CommunicationException ex) {
                Server.logDebug("DNS service '" + zone + "' unreachable.");
                return false;
            } catch (ServiceUnavailableException ex) {
                Server.logDebug("DNS service '" + zone + "' unavailable.");
                return false;
            } catch (NameNotFoundException ex) {
                // No listado.
                return false;
            } catch (NamingException ex) {
                Server.logError(ex);
                return false;
            }
        }
    }

    public static String getResult(String ip, String zone) {
        String host = Reverse.getHostReverse(ip, zone);
        if (host == null) {
            return null;
        } else {
            try {
                for (String result : getIPv4Set(host)) {
                    if (result.startsWith("127.0.")) {
                        return result;
                    }
                }
                return null;
            } catch (CommunicationException ex) {
                Server.logDebug("DNS service '" + zone + "' unreachable.");
                return null;
            } catch (ServiceUnavailableException ex) {
                Server.logDebug("DNS service '" + zone + "' unavailable.");
                return null;
            } catch (NameNotFoundException ex) {
                // No listado.
                return null;
            } catch (NamingException ex) {
                Server.logError(ex);
                return null;
            }
        }
    }

    public static String getHostReverse(String ip, String zone) {
        if (SubnetIPv4.isValidIPv4(ip)) {
            String reverse = zone;
            byte[] address = SubnetIPv4.split(ip);
            for (byte octeto : address) {
                reverse = ((int) octeto & 0xFF) + "." + reverse;
            }
            return reverse;
        } else if (SubnetIPv6.isValidIPv6(ip)) {
            String reverse = zone;
            byte[] address = SubnetIPv6.splitByte(ip);
            for (byte octeto : address) {
                String hexPart = Integer.toHexString((int) octeto & 0xFF);
                if (hexPart.length() == 1) {
                    hexPart = "0" + hexPart;
                }
                for (char digit : hexPart.toCharArray()) {
                    reverse = digit + "." + reverse;
                }
            }
            return reverse;
        } else {
            return null;
        }
    }

    public static boolean isInexistentDomain(String hostname) {
        try {
            hostname = Domain.normalizeHostname(hostname, false);
            Server.getAttributesDNS(hostname, new String[] { "NS" });
            return false;
        } catch (NameNotFoundException ex) {
            return true;
        } catch (NamingException ex) {
            return false;
        }
    }

    public static boolean hasValidNameServers(String hostname) throws NamingException {
        if ((hostname = Domain.normalizeHostname(hostname, false)) == null) {
            return false;
        } else {
            try {
                Attributes attributesNS = Server.getAttributesDNS(hostname, new String[] { "NS" });
                if (attributesNS != null) {
                    Enumeration enumerationNS = attributesNS.getAll();
                    while (enumerationNS.hasMoreElements()) {
                        Attribute attributeNS = (Attribute) enumerationNS.nextElement();
                        NamingEnumeration enumeration = attributeNS.getAll();
                        while (enumeration.hasMoreElements()) {
                            String ns = (String) enumeration.next();
                            if (Domain.isHostname(ns)) {
                                return true;
                            }
                        }
                    }
                }
                return false;
            } catch (NameNotFoundException ex) {
                return false;
            }
        }
    }

    public static TreeSet<String> getAddressSet(String hostname) throws NamingException {
        if ((hostname = Domain.normalizeHostname(hostname, false)) == null) {
            return null;
        } else {
            TreeSet<String> ipSet = new TreeSet<String>();
            Attributes attributesA = Server.getAttributesDNS(hostname, new String[] { "A" });
            if (attributesA != null) {
                Enumeration enumerationA = attributesA.getAll();
                while (enumerationA.hasMoreElements()) {
                    Attribute attributeA = (Attribute) enumerationA.nextElement();
                    NamingEnumeration enumeration = attributeA.getAll();
                    while (enumeration.hasMoreElements()) {
                        String address = (String) enumeration.next();
                        if (SubnetIPv4.isValidIPv4(address)) {
                            address = SubnetIPv4.normalizeIPv4(address);
                            ipSet.add(address);
                        }
                    }
                }
            }
            Attributes attributesAAAA = Server.getAttributesDNS(hostname, new String[] { "AAAA" });
            if (attributesAAAA != null) {
                Enumeration enumerationAAAA = attributesAAAA.getAll();
                while (enumerationAAAA.hasMoreElements()) {
                    Attribute attributeAAAA = (Attribute) enumerationAAAA.nextElement();
                    NamingEnumeration enumeration = attributeAAAA.getAll();
                    while (enumeration.hasMoreElements()) {
                        String address = (String) enumeration.next();
                        if (SubnetIPv6.isValidIPv6(address)) {
                            address = SubnetIPv6.normalizeIPv6(address);
                            ipSet.add(address);
                        }
                    }
                }
            }
            return ipSet;
        }
    }

    public static TreeSet<String> getPointerSet(String host) throws NamingException {
        if (host == null) {
            return null;
        } else {
            TreeSet<String> reverseSet = new TreeSet<String>();
            if (Subnet.isValidIP(host)) {
                if (SubnetIPv4.isValidIPv4(host)) {
                    host = getHostReverse(host, "in-addr.arpa");
                } else if (SubnetIPv6.isValidIPv6(host)) {
                    host = getHostReverse(host, "ip6.arpa");
                }
            }
            Attributes atributes = Server.getAttributesDNS(host, new String[] { "PTR" });
            if (atributes != null) {
                Attribute attribute = atributes.get("PTR");
                if (attribute != null) {
                    for (int index = 0; index < attribute.size(); index++) {
                        host = (String) attribute.get(index);
                        if (host != null) {
                            host = host.trim();
                            if (host.endsWith(".")) {
                                int endIndex = host.length() - 1;
                                host = host.substring(0, endIndex);
                            }
                            if (Domain.isHostname(host)) {
                                host = Domain.normalizeHostname(host, true);
                                reverseSet.add(host);
                            }
                        }
                    }
                }
            }
            return reverseSet;
        }
    }

    public static ArrayList<String> getMXSet(String host) throws NamingException {
        TreeMap<Integer, TreeSet<String>> mxMap = new TreeMap<Integer, TreeSet<String>>();
        Attributes atributes = Server.getAttributesDNS(host, new String[] { "MX" });
        if (atributes == null || atributes.size() == 0) {
            atributes = Server.getAttributesDNS(host, new String[] { "CNAME" });
            Attribute attribute = atributes.get("CNAME");
            if (attribute != null) {
                String cname = (String) attribute.get(0);
                return getMXSet(cname);
            }
        } else {
            Attribute attribute = atributes.get("MX");
            if (attribute != null) {
                for (int index = 0; index < attribute.size(); index++) {
                    try {
                        String mx = (String) attribute.get(index);
                        int space = mx.indexOf(' ');
                        String value = mx.substring(0, space);
                        int priority = Integer.parseInt(value);
                        mx = mx.substring(space + 1);
                        int last = mx.length() - 1;
                        TreeSet<String> mxSet = mxMap.get(priority);
                        if (mxSet == null) {
                            mxSet = new TreeSet<String>();
                            mxMap.put(priority, mxSet);
                        }
                        if (Subnet.isValidIP(mx.substring(0, last))) {
                            mxSet.add(Subnet.normalizeIP(mx.substring(0, last)));
                        } else if (Domain.isHostname(mx)) {
                            mxSet.add(Domain.normalizeHostname(mx, true));
                        }
                    } catch (NumberFormatException ex) {
                    }
                }
            }
        }
        ArrayList<String> mxList = new ArrayList<String>();
        if (mxMap.isEmpty()) {
            // https://tools.ietf.org/html/rfc5321#section-5
            mxList.add(Domain.normalizeHostname(host, true));
        } else {
            for (int priority : mxMap.keySet()) {
                TreeSet<String> mxSet = mxMap.get(priority);
                for (String mx : mxSet) {
                    if (!mxList.contains(mx)) {
                        mxList.add(mx);
                    }
                }
            }
        }
        return mxList;
    }

    private static TreeSet<String> getIPv4Set(String host) throws NamingException {
        TreeSet<String> ipSet = new TreeSet<String>();
        Attributes atributes = Server.getAttributesDNS(host, new String[] { "A" });
        if (atributes != null) {
            Attribute attribute = atributes.get("A");
            if (attribute != null) {
                for (int index = 0; index < attribute.size(); index++) {
                    String ip = (String) attribute.get(index);
                    if (SubnetIPv4.isValidIPv4(ip)) {
                        ip = SubnetIPv4.normalizeIPv4(ip);
                        ipSet.add(ip);
                    }
                }
            }
        }
        return ipSet;
    }

    private static TreeSet<String> getIPv6Set(String host) throws NamingException {
        TreeSet<String> ipSet = new TreeSet<String>();
        Attributes atributes = Server.getAttributesDNS(host, new String[] { "AAAA" });
        if (atributes != null) {
            Attribute attribute = atributes.get("AAAA");
            if (attribute != null) {
                for (int index = 0; index < attribute.size(); index++) {
                    String ip = (String) attribute.get(index);
                    if (SubnetIPv6.isValidIPv6(ip)) {
                        ip = SubnetIPv6.normalizeIPv6(ip);
                        ipSet.add(ip);
                    }
                }
            }
        }
        return ipSet;
    }

    public void refresh() {
        long time = System.currentTimeMillis();
        try {
            String reverse;
            if (SubnetIPv4.isValidIPv4(ip)) {
                reverse = getHostReverse(ip, "in-addr.arpa");
            } else if (SubnetIPv6.isValidIPv6(ip)) {
                reverse = getHostReverse(ip, "ip6.arpa");
            } else {
                reverse = null;
            }
            if (reverse != null) {
                TreeSet<String> ptrSet = getPointerSet(reverse);
                this.addressSet = ptrSet;
                Server.logReverseDNS(time, ip, ptrSet.toString());
            }
        } catch (CommunicationException ex) {
            Server.logReverseDNS(time, ip, "TIMEOUT");
        } catch (ServiceUnavailableException ex) {
            Server.logReverseDNS(time, ip, "SERVFAIL");
        } catch (NameNotFoundException ex) {
            this.addressSet = null;
            Server.logReverseDNS(time, ip, "NXDOMAIN");
        } catch (NamingException ex) {
            this.addressSet = null;
            Server.logReverseDNS(time, ip, "ERROR " + ex.getClass() + " " + ex.getExplanation());
        } finally {
            this.queryCount = 0;
            CHANGED = true;
        }
    }

    public boolean isExpired7() {
        return System.currentTimeMillis() - lastQuery > 604800000;
    }

    public boolean isExpired14() {
        return System.currentTimeMillis() - lastQuery > 1209600000;
    }

    public static String getValidHostname(String ip) {
        Reverse reverse = Reverse.get(ip);
        if (reverse == null) {
            return null;
        } else {
            String hostname = reverse.getAddressOnly();
            if (SPF.matchHELO(ip, hostname)) {
                return hostname;
            } else {
                return null;
            }
        }
    }

    public static String getHostname(String ip) {
        Reverse reverse = Reverse.get(ip);
        if (reverse == null) {
            return null;
        } else {
            return reverse.getAddressOnly();
        }
    }

    public static Reverse get(String ip) {
        return get(ip, false);
    }

    public static Reverse get(String ip, boolean refresh) {
        if ((ip = Subnet.normalizeIP(ip)) == null) {
            return null;
        } else {
            Reverse reverse = getExact(ip);
            if (reverse == null) {
                reverse = new Reverse(ip);
                putExact(ip, reverse);
            } else if (refresh) {
                reverse.refresh();
            } else if (reverse.isExpired7()) {
                reverse.refresh();
            } else {
                addQuery(reverse);
            }
            return reverse;
        }
    }

    /**
     * Atualiza o registro mais consultado.
     */
    public static void refreshLast() {
        Reverse reverseMax = getRefreshReverse();
        if (reverseMax == null) {
            for (String ip : keySet()) {
                Reverse reverse = getExact(ip);
                if (reverse != null) {
                    if (reverseMax == null) {
                        reverseMax = reverse;
                    } else if (reverseMax.getQueryCount() < reverse.getQueryCount()) {
                        reverseMax = reverse;
                    }
                }
            }
        }
        if (reverseMax != null && reverseMax.getQueryCount() > 3) {
            reverseMax.refresh();
        }
    }

    public static void dropExpired() {
        for (String ip : keySet()) {
            long time = System.currentTimeMillis();
            Reverse reverse = getExact(ip);
            if (reverse != null && reverse.isExpired14()) {
                reverse = dropExact(ip);
                if (reverse != null) {
                    Server.logReverseDNS(time, ip, "EXPIRED");
                }
            }
        }
    }

    private static boolean isChanged() {
        return CHANGED;
    }

    private static void setStored() {
        CHANGED = false;
    }

    public static void store() {
        if (isChanged()) {
            try {
                //                Server.logTrace("storing reverse.map");
                long time = System.currentTimeMillis();
                File file = new File("./data/reverse.map");
                HashMap<String, Reverse> map = getMap();
                FileOutputStream outputStream = new FileOutputStream(file);
                try {
                    SerializationUtils.serialize(map, outputStream);
                    setStored();
                } finally {
                    outputStream.close();
                }
                Server.logStore(time, file);
            } catch (Exception ex) {
                Server.logError(ex);
            }
        }
    }

    public static void load() {
        long time = System.currentTimeMillis();
        File file = new File("./data/reverse.map");
        if (file.exists()) {
            try {
                HashMap<String, Object> map;
                FileInputStream fileInputStream = new FileInputStream(file);
                try {
                    map = SerializationUtils.deserialize(fileInputStream);
                } finally {
                    fileInputStream.close();
                }
                for (String key : map.keySet()) {
                    Object value = map.get(key);
                    if (value instanceof Reverse) {
                        Reverse reverse = (Reverse) value;
                        putExact(key, reverse);
                    }
                }
                setStored();
                Server.logLoad(time, file);
            } catch (Exception ex) {
                Server.logError(ex);
            }
        }
    }
}