net.spfbl.dnsbl.QueryDNSBL.java Source code

Java tutorial

Introduction

Here is the source code for net.spfbl.dnsbl.QueryDNSBL.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.dnsbl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import net.spfbl.core.Server;
import net.spfbl.spf.SPF;
import net.spfbl.whois.SubnetIPv4;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import net.spfbl.core.Analise;
import net.spfbl.data.Block;
import net.spfbl.core.Client;
import net.spfbl.core.Client.Permission;
import net.spfbl.core.Core;
import net.spfbl.whois.Domain;
import net.spfbl.whois.SubnetIPv6;
import org.apache.commons.lang3.SerializationUtils;
import org.xbill.DNS.ARecord;
import org.xbill.DNS.DClass;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Header;
import org.xbill.DNS.Message;
import org.xbill.DNS.NSRecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
import org.xbill.DNS.SOARecord;
import org.xbill.DNS.Section;
import org.xbill.DNS.TXTRecord;
import org.xbill.DNS.Type;
import org.xbill.DNS.WireParseException;

/**
 * Servidor de consulta DNSBL.
 *
 * @author Leandro Carlos Rodrigues <leandro@spfbl.net>
 */
public final class QueryDNSBL extends Server {

    private final int PORT;
    private final DatagramSocket SERVER_SOCKET;

    /**
     * Mapa para cache dos registros DNS consultados.
     */
    private static final HashMap<String, ServerDNSBL> MAP = new HashMap<String, ServerDNSBL>();

    private static final long SERIAL = 2015102500;

    /**
     * Flag que indica se o cache foi modificado.
     */
    private static boolean CHANGED = false;

    private static ServerDNSBL dropExact(String token) {
        ServerDNSBL ret = MAP.remove(token);
        if (ret == null) {
            return null;
        } else {
            CHANGED = true;
            return ret;
        }
    }

    private static boolean putExact(String key, ServerDNSBL value) {
        ServerDNSBL ret = MAP.put(key, value);
        if (value.equals(ret)) {
            return false;
        } else {
            CHANGED = true;
            return true;
        }
    }

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

    public static HashMap<String, ServerDNSBL> getMap() {
        HashMap<String, ServerDNSBL> map = new HashMap<String, ServerDNSBL>();
        map.putAll(MAP);
        return map;
    }

    private static boolean containsExact(String host) {
        return MAP.containsKey(host);
    }

    private static ServerDNSBL getExact(String host) {
        return MAP.get(host);
    }

    public static TreeSet<ServerDNSBL> getValues() {
        TreeSet<ServerDNSBL> serverSet = new TreeSet<ServerDNSBL>();
        serverSet.addAll(MAP.values());
        return serverSet;
    }

    /**
     * Adiciona um registro DNS no mapa de cache.
     */
    public static boolean add(String hostname, String message) {
        if (hostname == null) {
            return false;
        } else if (Domain.isHostname(hostname)) {
            hostname = Domain.normalizeHostname(hostname, true);
            ServerDNSBL server = new ServerDNSBL(hostname, message);
            return putExact(hostname, server);
        } else {
            return false;
        }
    }

    public static boolean set(String hostname, String message) {
        if (hostname == null) {
            return false;
        } else if (Domain.isHostname(hostname)) {
            hostname = Domain.normalizeHostname(hostname, true);
            ServerDNSBL server = getExact(hostname);
            if (server == null) {
                return false;
            } else {
                server.setMessage(message);
                return true;
            }
        } else {
            return false;
        }
    }

    private static ServerDNSBL get(String hostname) {
        if (hostname == null) {
            return null;
        } else if (Domain.isHostname(hostname)) {
            hostname = Domain.normalizeHostname(hostname, true);
            return getExact(hostname);
        } else {
            return null;
        }
    }

    public static TreeSet<ServerDNSBL> dropAll() {
        TreeSet<ServerDNSBL> serverSet = new TreeSet<ServerDNSBL>();
        for (ServerDNSBL server : getValues()) {
            if (server != null) {
                String hostname = server.getHostName();
                server = dropExact(hostname);
                if (server != null) {
                    serverSet.add(server);
                }
            }
        }
        return serverSet;
    }

    public static ServerDNSBL drop(String hostname) {
        if (hostname == null) {
            return null;
        } else if (Domain.isHostname(hostname)) {
            hostname = Domain.normalizeHostname(hostname, true);
            return dropExact(hostname);
        } else {
            return null;
        }
    }

    public static void store() {
        if (CHANGED) {
            try {
                long time = System.currentTimeMillis();
                File file = new File("./data/dnsbl.map");
                HashMap<String, ServerDNSBL> map = getMap();
                FileOutputStream outputStream = new FileOutputStream(file);
                try {
                    SerializationUtils.serialize(map, outputStream);
                    CHANGED = false;
                } 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/dnsbl.map");
        if (file.exists()) {
            try {
                Map<String, ServerDNSBL> map;
                FileInputStream fileInputStream = new FileInputStream(file);
                try {
                    map = SerializationUtils.deserialize(fileInputStream);
                } finally {
                    fileInputStream.close();
                }
                for (String key : map.keySet()) {
                    ServerDNSBL value = map.get(key);
                    putExact(key, value);
                }
                CHANGED = false;
                Server.logLoad(time, file);
            } catch (Exception ex) {
                Server.logError(ex);
            }
        } else if ((file = new File("./data/dns.map")).exists()) {
            try {
                HashMap<String, InetAddress> map;
                FileInputStream fileInputStream = new FileInputStream(file);
                try {
                    map = SerializationUtils.deserialize(fileInputStream);
                } finally {
                    fileInputStream.close();
                }
                for (String key : map.keySet()) {
                    String message = "<IP> is listed in this server.";
                    ServerDNSBL server = new ServerDNSBL(key, message);
                    putExact(key, server);
                    CHANGED = true;
                }
                Server.logLoad(time, file);
            } catch (Exception ex) {
                Server.logError(ex);
            }
        }
    }

    /**
     * Configurao e intanciamento do servidor.
     * @throws java.net.SocketException se houver falha durante o bind.
     */
    public QueryDNSBL(int port) throws SocketException {
        super("SERVERDNS");
        setPriority(Thread.NORM_PRIORITY);
        // Criando conexes.
        Server.logDebug("binding DNSBL socket on port " + port + "...");
        PORT = port;
        SERVER_SOCKET = new DatagramSocket(port);
    }

    private int CONNECTION_ID = 1;

    /**
     * Representa uma conexo ativa.
     * Serve para processar todas as requisies.
     */
    private class Connection extends Thread {

        /**
         * O poll de pacotes de consulta a serem processados.
         */
        private DatagramPacket PACKET = null;

        private final Semaphore SEMAPHORE = new Semaphore(0);

        private long time = 0;

        public Connection() {
            super("DNSUDP" + Core.CENTENA_FORMAT.format(CONNECTION_ID++));
            // Toda connexo recebe prioridade mnima.
            setPriority(Thread.NORM_PRIORITY);
        }

        /**
         * Processa um pacote de consulta.
         * @param packet o pacote de consulta a ser processado.
         */
        private void process(DatagramPacket packet, long time) {
            this.PACKET = packet;
            this.time = time;
            this.SEMAPHORE.release();
        }

        private boolean isTimeout() {
            if (time == 0) {
                return false;
            } else {
                int interval = (int) (System.currentTimeMillis() - time) / 1000;
                return interval > 20;
            }
        }

        /**
         * Fecha esta conexo liberando a thread.
         */
        private void close() {
            Server.logDebug("closing " + getName() + "...");
            PACKET = null;
            SEMAPHORE.release();
        }

        public DatagramPacket getPacket() {
            if (QueryDNSBL.this.continueListenning()) {
                try {
                    SEMAPHORE.acquire();
                    return PACKET;
                } catch (InterruptedException ex) {
                    return null;
                }
            } else {
                return null;
            }
        }

        public void clearPacket() {
            time = 0;
            PACKET = null;
        }

        /**
         * Processamento da consulta e envio do resultado.
         * Aproveita a thead para realizar procedimentos em background.
         */
        @Override
        public void run() {
            DatagramPacket packet;
            while ((packet = getPacket()) != null) {
                InetAddress ipAddress = packet.getAddress();
                String origin = ipAddress.getHostAddress();
                String query = "ERROR";
                String result = "IGNORED";
                try {
                    byte[] data = packet.getData();
                    // Processando consulta DNS.
                    Message message = new Message(data);
                    Header header = message.getHeader();
                    Record question = message.getQuestion();
                    Name name = question.getName();
                    String type = Type.string(question.getType());
                    query = name.toString();
                    // Identificao do cliente.
                    Client client = Client.create(ipAddress, "DNSBL");
                    if (client == null) {
                        result = "IGNORED";
                    } else if (client.hasPermission(Permission.NONE)) {
                        client.addQuery();
                        origin += ' ' + client.getDomain();
                        result = "IGNORED";
                    } else if (client.isAbusing()) {
                        client.addQuery();
                        origin += ' ' + client.getDomain();
                        result = "IGNORED";
                    } else {
                        client.addQuery();
                        origin += ' ' + client.getDomain();
                        long ttl = 3600; // Uma hora padro.
                        String host = Domain.extractHost(query, false);
                        ServerDNSBL server = null;
                        String clientQuery = null;
                        if (host == null) {
                            result = "NXDOMAIN";
                        } else {
                            int index = host.length() - 1;
                            host = host.substring(0, index);
                            String hostname = null;
                            String reverse = "";
                            if ((server = getExact('.' + host)) == null) {
                                while ((index = host.lastIndexOf('.', index)) != -1) {
                                    reverse = host.substring(0, index);
                                    hostname = host.substring(index);
                                    if ((server = getExact(hostname)) == null) {
                                        index--;
                                    } else {
                                        break;
                                    }
                                }
                            }
                            if (server == null) {
                                // No existe servidor DNSBL cadastrado.
                                result = "NXDOMAIN";
                            } else if (type.equals("A") && server.isHostName(host)) {
                                // O A  o prprio servidor.
                                if ((result = Core.getHostname()) == null) {
                                    result = "NXDOMAIN";
                                } else {
                                    InetAddress address = InetAddress.getByName(result);
                                    result = address.getHostAddress();
                                }
                            } else if (type.equals("NS") && server.isHostName(host)) {
                                // O NS  o prprio servidor.
                                if ((result = Core.getHostname()) == null) {
                                    result = "NXDOMAIN";
                                } else {
                                    result += '.';
                                }
                            } else if (host.equals(hostname)) {
                                // Consulta do prprio hostname do servidor.
                                result = "NXDOMAIN";
                            } else if (reverse.length() == 0) {
                                // O reverso  invlido.
                                result = "NXDOMAIN";
                            } else if (SubnetIPv4.isValidIPv4(reverse)) {
                                // A consulta  um IPv4.
                                clientQuery = SubnetIPv4.reverseToIPv4(reverse);
                                Analise.processToday(clientQuery);
                                if (clientQuery.equals("127.0.0.1")) {
                                    // Consulta de teste para negativo.
                                    result = "NXDOMAIN";
                                } else if (clientQuery.equals("127.0.0.2")) {
                                    // Consulta de teste para positivio.
                                    result = "127.0.0.2";
                                } else if (Block.containsCIDR(clientQuery)) {
                                    result = "127.0.0.2";
                                    ttl = 259200; // Trs dias.
                                } else if (SPF.isRed(clientQuery)) {
                                    result = "127.0.0.2";
                                    ttl = 86400; // Um dia.
                                } else {
                                    result = "NXDOMAIN";
                                }
                            } else if (SubnetIPv6.isReverseIPv6(reverse)) {
                                // A consulta  um IPv6.
                                clientQuery = SubnetIPv6.reverseToIPv6(reverse);
                                Analise.processToday(clientQuery);
                                if (Block.containsCIDR(clientQuery)) {
                                    result = "127.0.0.2";
                                    ttl = 259200; // Trs dias.
                                } else if (SPF.isRed(clientQuery)) {
                                    result = "127.0.0.2";
                                    ttl = 86400; // Um dia.
                                } else {
                                    result = "NXDOMAIN";
                                }
                            } else if ((clientQuery = server.extractDomain(host)) != null) {
                                if (Block.containsDomain(clientQuery)) {
                                    result = "127.0.0.2";
                                    ttl = 259200; // Trs dias.
                                } else if (SPF.isRed(clientQuery)) {
                                    result = "127.0.0.2";
                                    ttl = 86400; // Um dia.
                                } else {
                                    result = "NXDOMAIN";
                                }
                                clientQuery = Domain.normalizeHostname(clientQuery, false);
                            } else {
                                // No est listado.
                                result = "NXDOMAIN";
                            }
                        }
                        if (type.equals("TXT") && result.equals("127.0.0.2")) {
                            if (server == null) {
                                result = "NXDOMAIN";
                            } else {
                                String information = server.getMessage(clientQuery);
                                if (information == null) {
                                    result = "NXDOMAIN";
                                } else {
                                    result = information;
                                }
                            }
                        }
                        // Alterando mensagem DNS para resposta.
                        header.setFlag(Flags.QR);
                        header.setFlag(Flags.AA);
                        if (result.equals("NXDOMAIN")) {
                            header.setRcode(Rcode.NXDOMAIN);
                            if (server != null) {
                                long refresh = 1800;
                                long retry = 900;
                                long expire = 604800;
                                long minimum = 300;
                                name = new Name(server.getHostName().substring(1) + '.');
                                SOARecord soa = new SOARecord(name, DClass.IN, ttl, name, name, SERIAL, refresh,
                                        retry, expire, minimum);
                                message.addRecord(soa, Section.AUTHORITY);
                            }
                        } else if (type.equals("TXT")) {
                            TXTRecord txt = new TXTRecord(name, DClass.IN, ttl, result);
                            message.addRecord(txt, Section.ANSWER);
                        } else if (result.equals("127.0.0.2")) {
                            InetAddress address = InetAddress.getByName(result);
                            ARecord a = new ARecord(name, DClass.IN, ttl, address);
                            message.addRecord(a, Section.ANSWER);
                        } else if (type.equals("NS")) {
                            Name hostname = Name.fromString(result);
                            NSRecord ns = new NSRecord(name, DClass.IN, ttl, hostname);
                            message.addRecord(ns, Section.ANSWER);
                        } else {
                            InetAddress address = InetAddress.getByName(result);
                            ARecord a = new ARecord(name, DClass.IN, ttl, address);
                            message.addRecord(a, Section.ANSWER);
                        }
                        result = ttl + " " + result;
                        // Enviando resposta.
                        int portDestiny = packet.getPort();
                        byte[] sendData = message.toWire();
                        DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, ipAddress,
                                portDestiny);
                        SERVER_SOCKET.send(sendPacket);
                    }
                    query = type + " " + query;
                } catch (SocketException ex) {
                    // Houve fechamento do socket.
                    result = "CLOSED";
                } catch (WireParseException ex) {
                    // Ignorar consultas invlidas.
                    query = "UNPARSEABLE";
                    result = "IGNORED";
                } catch (Exception ex) {
                    Server.logError(ex);
                    result = "ERROR";
                } finally {
                    Server.logQueryDNSBL(time, origin, query, result);
                    clearPacket();
                    // Oferece a conexo ociosa na ltima posio da lista.
                    offer(this);
                    CONNECION_SEMAPHORE.release();
                }
            }
            CONNECTION_COUNT--;
        }
    }

    /**
     * Pool de conexes ativas.
     */
    private final LinkedList<Connection> CONNECTION_POLL = new LinkedList<Connection>();
    private final LinkedList<Connection> CONNECTION_USE = new LinkedList<Connection>();

    /**
     * Semforo que controla o pool de conexes.
     */
    private final Semaphore CONNECION_SEMAPHORE = new Semaphore(0);

    /**
     * Quantidade total de conexes intanciadas.
     */
    private int CONNECTION_COUNT = 0;

    private static byte CONNECTION_LIMIT = 16;

    public static void setConnectionLimit(String limit) {
        if (limit != null && limit.length() > 0) {
            try {
                setConnectionLimit(Integer.parseInt(limit));
            } catch (Exception ex) {
                Server.logError("invalid DNSBL connection limit '" + limit + "'.");
            }
        }
    }

    public static void setConnectionLimit(int limit) {
        if (limit < 1 || limit > Byte.MAX_VALUE) {
            Server.logError("invalid DNSBL connection limit '" + limit + "'.");
        } else {
            CONNECTION_LIMIT = (byte) limit;
        }
    }

    private synchronized Connection poll() {
        return CONNECTION_POLL.poll();
    }

    private synchronized Connection pollUsing() {
        return CONNECTION_USE.poll();
    }

    private synchronized void use(Connection connection) {
        CONNECTION_USE.offer(connection);
    }

    private synchronized void offer(Connection connection) {
        CONNECTION_USE.remove(connection);
        CONNECTION_POLL.offer(connection);
    }

    private synchronized void offerUsing(Connection connection) {
        CONNECTION_USE.offer(connection);
    }

    public void interruptTimeout() {
        Connection connection = pollUsing();
        if (connection != null) {
            if (connection.isTimeout()) {
                offerUsing(connection);
                connection.interrupt();
            } else {
                offerUsing(connection);
            }
        }
    }

    /**
     * Coleta uma conexo ociosa ou inicia uma nova.
     * @return uma conexo ociosa ou nova se no houver ociosa.
     */
    private Connection pollConnection() {
        try {
            if (CONNECION_SEMAPHORE.tryAcquire(1, TimeUnit.SECONDS)) {
                Connection connection = poll();
                if (connection == null) {
                    CONNECION_SEMAPHORE.release();
                } else {
                    use(connection);
                }
                return connection;
                //            } else if (Core.hasLowMemory()) {
                //                return null;
            } else if (CONNECTION_COUNT < CONNECTION_LIMIT) {
                // Cria uma nova conexo se no houver conecxes ociosas.
                // O servidor aumenta a capacidade conforme a demanda.
                Server.logDebug("creating DNSUDP" + Core.CENTENA_FORMAT.format(CONNECTION_ID) + "...");
                Connection connection = new Connection();
                connection.start();
                CONNECTION_COUNT++;
                return connection;
            } else {
                // Se no houver liberao, ignorar consulta DNS.
                // O MX que fizer a consulta ter um TIMEOUT
                // considerando assim o IP como no listado.
                return null;
            }
        } catch (Exception ex) {
            Server.logError(ex);
            return null;
        }
    }

    /**
     * Inicializao do servio.
     */
    @Override
    public void run() {
        try {
            Server.logInfo("listening DNSBL on UDP port " + PORT + ".");
            while (continueListenning()) {
                try {
                    byte[] receiveData = new byte[1024];
                    DatagramPacket packet = new DatagramPacket(receiveData, receiveData.length);
                    SERVER_SOCKET.receive(packet);
                    if (continueListenning()) {
                        long time = System.currentTimeMillis();
                        Connection connection = pollConnection();
                        if (connection == null) {
                            InetAddress ipAddress = packet.getAddress();
                            String result = "TOO MANY CONNECTIONS\n";
                            Server.logQueryDNSBL(time, ipAddress, null, result);
                        } else {
                            try {
                                connection.process(packet, time);
                            } catch (IllegalThreadStateException ex) {
                                // Houve problema na liberao do processo.
                                InetAddress ipAddress = packet.getAddress();
                                String result = "ERROR: FATAL\n";
                                Server.logError(ex);
                                Server.logQueryDNSBL(time, ipAddress, null, result);
                                offer(connection);
                            }
                        }
                    }
                } catch (SocketException ex) {
                    // Conexo fechada externamente pelo mtodo close().
                }
            }
        } catch (Exception ex) {
            Server.logError(ex);
        } finally {
            Server.logInfo("querie DNSBL server closed.");
        }
    }

    /**
     * Fecha todas as conexes e finaliza o servidor UDP.
     * @throws Exception se houver falha em algum fechamento.
     */
    @Override
    protected void close() {
        while (CONNECTION_COUNT > 0) {
            try {
                Connection connection = poll();
                if (connection == null) {
                    CONNECION_SEMAPHORE.tryAcquire(500, TimeUnit.MILLISECONDS);
                } else {
                    connection.close();
                }
            } catch (Exception ex) {
                Server.logError(ex);
            }
        }
        Server.logDebug("unbinding DSNBL socket on port " + PORT + "...");
        SERVER_SOCKET.close();
    }
}