Java tutorial
/* * 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.spf; import com.sun.mail.util.MailConnectException; import net.spfbl.core.Core; import net.spfbl.core.NormalDistribution; import net.spfbl.whois.Domain; import net.spfbl.core.ProcessException; import net.spfbl.core.Server; import net.spfbl.whois.Owner; import net.spfbl.whois.Subnet; import net.spfbl.whois.SubnetIPv4; import net.spfbl.whois.SubnetIPv6; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.Serializable; import java.net.Inet6Address; import java.net.InetAddress; import java.net.URLEncoder; import java.net.UnknownHostException; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; import java.util.Locale; import java.util.Map; import java.util.NavigableMap; import java.util.Properties; import java.util.StringTokenizer; import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.mail.Message; import javax.mail.SendFailedException; import javax.mail.Session; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.naming.CommunicationException; import javax.naming.InvalidNameException; 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 javax.naming.directory.InvalidAttributeIdentifierException; import net.spfbl.core.Action; import net.spfbl.core.Analise; import net.spfbl.core.Client; import net.spfbl.data.Block; import net.spfbl.core.Defer; import net.spfbl.data.Ignore; import net.spfbl.data.NoReply; import net.spfbl.core.Peer; import net.spfbl.data.Provider; import net.spfbl.core.Reverse; import net.spfbl.core.User; import net.spfbl.data.Generic; import net.spfbl.data.Trap; import net.spfbl.data.White; import net.spfbl.http.ServerHTTP; import org.apache.commons.lang3.SerializationUtils; /** * Representa o registro SPF de um deterninado hostname. * * Implementao da RFC 7208, com algumas modificaes para atender condies * especficas. * * Quando a consulta feita, o resultado do SPF considerado para determinar o * responsvel pela mensagem. Uma vez encontrado o responsvel, um ticket SPFBL * gerado atravs de criptografia na base 64. Este ticket enviado juntamente * o qualificador SPF da consulta. O cliente da consulta deve extrair o ticket * do resultado e adicionar no cabealho da mensagem utilizando o campo * "Received-SPFBL". * * A regra de determinao de responsabilidade usada para gerar o ticket SPFBL * e funciona da seguinte forma: 1. Se retornar PASS, o remetente o * responsvel pela mensagem ou 2. Caso contrrio, o hostname responsvel pela * mensagem. * * No primeiro caso, onde o remetente responsvel pela mensagem, o ticket * gerado com a seguinte regra: 1. Se o domnio do rementente estiver na lista * de provedores, ento o endereo de e-mail completo utilizado ou 2. Caso * contrrio, o hostname e domnio do rementente so utilizados. * * No segundo caso, onde o hostname responsvel pela mensagem, o ticket * gerado com a seguinte regra: 1. Se o HELO apontar para o IP, ento o prprio * HELO e o domnio do HELO so utilizados ou 2. Caso contrrio, o IP * utilizado. * * Todas as consultas so registradas numa distribuio de probabilidade, onde * possvel alternar de HAM para SPAM utilizando o ticket gerado. Uma vez * recebida a reclamao com o ticket, o servio descriptografa o ticket e * extrai os responsaveis pelo envio. * * @author Leandro Carlos Rodrigues <leandro@spfbl.net> */ public final class SPF implements Serializable { private static final long serialVersionUID = 1L; private final String hostname; private String redirect = null; private String explanation = null; private ArrayList<Mechanism> mechanismList = null; private Qualifier all = null; // Qualificador do mecanismo all. private boolean error = false; // Se houve erro de sintaxe. private int queries = 0; // Contador de consultas. private int nxdomain = 0; // Contador de inexistncia de domnio. private long lastRefresh = 0; // ltima vez que houve atualizao do registro em milisegundos. private static final int REFRESH_TIME = 7; // Prazo mximo que o registro deve permanecer em cache em dias. private SPF(String hostname) throws ProcessException { this.hostname = hostname; // Sempre usar best-guess em caso de // indisponibilidade de DNS na primeira consulta. refresh(false, true); } /** * Consulta o registro SPF nos registros DNS do domnio. Se houver mais de * dois registros diferentes, realiza o merge do forma a retornar um nico * registro. * * @param hostname o nome do hostname para consulta do SPF. * @param bgWhenUnavailable usar best-guess quando houver erro temporrio * para alcanar o registro. * @return o registro SPF consertado, padronuzado e mergeado. * @throws ProcessException */ private static LinkedList<String> getRegistrySPF(String hostname, boolean bgWhenUnavailable) throws ProcessException { LinkedList<String> registryList = new LinkedList<String>(); try { // if (CacheGuess.containsExact(hostname)) { // // Sempre que houver registro de // // chute, sobrepor registro atual. // registryList.add(CacheGuess.get(hostname)); // } else { // // Caso contrrio procurar nos // // registros oficiais do domnio. try { Attributes attributes = Server.getAttributesDNS(hostname, new String[] { "SPF" }); Attribute attribute = attributes.get("SPF"); if (attribute != null) { for (int index = 0; index < attribute.size(); index++) { String registry = (String) attribute.get(index); if (registry.contains("v=spf1 ")) { registry = fixRegistry(registry); if (!registryList.contains(registry)) { registryList.add(registry); } } } } } catch (InvalidAttributeIdentifierException ex) { // No encontrou registro SPF. } if (registryList.isEmpty()) { try { Attributes attributes = Server.getAttributesDNS(hostname, new String[] { "TXT" }); Attribute attribute = attributes.get("TXT"); if (attribute != null) { for (int index = 0; index < attribute.size(); index++) { String registry = (String) attribute.get(index); if (registry.contains("v=spf1 ")) { registry = fixRegistry(registry); if (!registryList.contains(registry)) { registryList.add(registry); } } } } } catch (InvalidAttributeIdentifierException ex2) { // No encontrou registro TXT. } } // } if (registryList.isEmpty()) { // hostname = "." + hostname; // if (CacheGuess.containsExact(hostname)) { // // Significa que um palpite SPF // // foi registrado para este hostname. // // Neste caso utilizar o paltpite especfico. // registryList.add(CacheGuess.get(hostname)); // } else { // // Se no hoouver palpite especfico para o hostname, // // utilizar o palpite padro, porm adaptado para IPv6. // // http://www.openspf.org/FAQ/Best_guess_record // registryList.add(CacheGuess.BEST_GUESS); // } // Como o domnio no tem registro SPF, // utilizar um registro SPF de chute do sistema. String guess = CacheGuess.get(hostname); registryList.add(guess); } return registryList; } catch (NameNotFoundException ex) { return null; } catch (NamingException ex) { if (bgWhenUnavailable) { // Na indisponibilidade do DNS // utilizar um registro SPF de chute do sistema. String guess = CacheGuess.get(hostname); registryList.add(guess); return registryList; } else if (ex instanceof CommunicationException) { throw new ProcessException("ERROR: DNS UNAVAILABLE"); } else { throw new ProcessException("ERROR: DNS UNAVAILABLE", ex); } } catch (Exception ex) { throw new ProcessException("ERROR: FATAL", ex); } } /** * Algoritmo para consertar e padronizar o registro SPF. * * @param registry o registro SPF original. * @return o registro SPF consertado e padronizado. */ private static String fixRegistry(String registry) { String vesion = "v=spf1"; String all = null; String redirect = null; String explanation = null; LinkedList<String> midleList = new LinkedList<String>(); LinkedList<String> errorList = new LinkedList<String>(); registry = registry.replace("\\\"", "\""); registry = registry.replace("\" \"", ""); registry = registry.replace("\"", ""); registry = registry.toLowerCase(); StringTokenizer tokenizer = new StringTokenizer(registry, " "); while (tokenizer.hasMoreTokens()) { Boolean valid; String token = tokenizer.nextToken(); if (token.equals("v=spf1")) { vesion = token; valid = null; } else if (token.startsWith("redirect=")) { redirect = token; valid = null; } else if (token.startsWith("exp=")) { explanation = token; valid = null; } else if (token.equals("v=msv1")) { valid = true; } else if (token.startsWith("t=") && token.length() == 32) { valid = true; } else if (isMechanismMiddle(token)) { valid = true; } else if (isMechanismAll(token)) { all = token; valid = null; } else { valid = false; } if (valid == null) { mergeMechanism(midleList, errorList); } else if (valid == true) { mergeMechanism(midleList, errorList); if (!midleList.contains(token)) { // No considera tokens repetidos. midleList.add(token); } } else if (valid == false) { errorList.add(token); } } registry = vesion; if (redirect == null) { for (String token : midleList) { registry += ' ' + token; } if (all != null) { registry += ' ' + all; } } else { registry += ' ' + redirect; } if (explanation != null) { registry += ' ' + explanation; } return registry; } /** * Merge nas listas de fixao de SPF. * * @param midleList lista dos mecanismos centrais. * @param errorList lista dos mecanismos com erro de sintaxe. */ private static void mergeMechanism(LinkedList<String> midleList, LinkedList<String> errorList) { while (!errorList.isEmpty()) { boolean fixed = false; if (errorList.size() > 1) { for (int index = 1; index < errorList.size(); index++) { String tokenFix = errorList.getFirst(); for (String tokenError : errorList.subList(1, index + 1)) { tokenFix += tokenError; } if (isMechanismMiddle(tokenFix)) { midleList.add(tokenFix); int k = 0; while (k++ <= index) { errorList.removeFirst(); } fixed = true; break; } } } if (!fixed) { // No foi capaz de corrigir o erro. midleList.add(errorList.removeFirst()); } } } /** * Verifica se o whois um mecanismo cental. * * @param token o whois do registro SPF. * @return verdadeiro se o whois um mecanismo cental. */ private static boolean isMechanismMiddle(String token) { if (isMechanismIPv4(token)) { return true; } else if (isMechanismIPv6(token)) { return true; } else if (isMechanismA(token)) { return true; } else if (isMechanismMX(token)) { return true; } else if (isMechanismPTR(token)) { return true; } else if (isMechanismExistis(token)) { return true; } else if (isMechanismInclude(token)) { return true; } else { return false; } } private boolean isInexistent() { return nxdomain > 0; } private boolean isDefinitelyInexistent() { // Se consultou mais de 32 vezes // seguidas com 3 retornos de inexistncia, // considerar como definitivamente inexistente. return nxdomain > 3 && queries > 32; } /** * Mtodo seguro para incrementar nxdomain sem deixar que ele se torne * negativo. */ private void addInexistent() { if (nxdomain < Integer.MAX_VALUE) { nxdomain++; } } private synchronized void updateLastRefresh() { this.queries = 0; this.lastRefresh = System.currentTimeMillis(); } /** * Atualiza o registro SPF de um hostname. * * @throws ProcessException se houver falha no processamento. */ private synchronized void refresh(boolean load, boolean bgWhenUnavailable) throws ProcessException { long time = System.currentTimeMillis(); LinkedList<String> registryList = getRegistrySPF(hostname, bgWhenUnavailable); if (registryList == null) { // Domnimo no encontrado. this.mechanismList = null; this.all = null; this.redirect = null; this.explanation = null; this.error = false; CacheSPF.CHANGED = true; this.addInexistent(); updateLastRefresh(); Server.logLookupSPF(time, hostname, "NXDOMAIN"); } else if (registryList.isEmpty()) { // Sem registro SPF. this.mechanismList = new ArrayList<Mechanism>(); this.all = null; this.redirect = null; this.explanation = null; this.error = false; CacheSPF.CHANGED = true; this.nxdomain = 0; updateLastRefresh(); Server.logLookupSPF(time, hostname, "NO REGISTRY"); } else { ArrayList<Mechanism> mechanismListIP = new ArrayList<Mechanism>(); ArrayList<Mechanism> mechanismListDNS = new ArrayList<Mechanism>(); ArrayList<Mechanism> mechanismListInclude = new ArrayList<Mechanism>(); ArrayList<Mechanism> mechanismListPTR = new ArrayList<Mechanism>(); TreeSet<String> visitedTokens = new TreeSet<String>(); Qualifier allLocal = null; String redirectLocal = null; String explanationLocal = null; boolean errorQuery = false; String fixed; String result = null; for (String registry : registryList) { boolean errorRegistry = false; StringTokenizer tokenizer = new StringTokenizer(registry, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (visitedTokens.contains(token)) { // Token j visitado. } else if (token.equals("spf1")) { // Nada deve ser feito. } else if (token.equals("v=spf1")) { // Nada deve ser feito. } else if (token.equals("v=msv1")) { // Nada deve ser feito. } else if (token.equals("+")) { // Ignorar qualificadores isolados. } else if (token.startsWith("t=") && token.length() == 32) { // Nada deve ser feito. } else if (isMechanismAll(token)) { // No permitir qualificadores permissivos para all. switch (token.charAt(0)) { case '-': allLocal = Qualifier.FAIL; break; case '~': allLocal = Qualifier.SOFTFAIL; break; default: allLocal = Qualifier.NEUTRAL; // Default qualifier or all. } } else if (isMechanismIPv4(token)) { mechanismListIP.add(new MechanismIPv4(token)); } else if (isMechanismIPv6(token)) { mechanismListIP.add(new MechanismIPv6(token)); } else if (isMechanismA(token)) { mechanismListDNS.add(new MechanismA(token, load)); } else if (isMechanismMX(token)) { mechanismListDNS.add(new MechanismMX(token, load)); } else if (isMechanismPTR(token)) { mechanismListPTR.add(new MechanismPTR(token)); } else if (isMechanismExistis(token)) { mechanismListDNS.add(new MechanismExists(token)); } else if (isMechanismInclude(token)) { mechanismListInclude.add(new MechanismInclude(token)); } else if (isModifierRedirect(token)) { int index = token.indexOf("=") + 1; redirectLocal = token.substring(index); } else if (isModifierExplanation(token)) { int index = token.indexOf("=") + 1; explanationLocal = token.substring(index); } else if ((fixed = extractIPv4CIDR(token)) != null) { // Tenta recuperar um erro de sintaxe. if (!visitedTokens.contains(token = "ip4:" + fixed)) { mechanismListIP.add(new MechanismIPv4(token)); } errorRegistry = true; } else if ((fixed = extractIPv6CIDR(token)) != null) { // Tenta recuperar um erro de sintaxe. if (!visitedTokens.contains(token = "ip4:" + fixed)) { mechanismListIP.add(new MechanismIPv6(token)); } errorRegistry = true; } else { // Um erro durante o processamento foi encontrado. Server.logDebug("SPF token not defined: " + token); errorRegistry = true; errorQuery = true; } visitedTokens.add(token); } if (result == null) { result = (errorRegistry ? "ERR" : "OK") + " \"" + registry + "\""; } else { result += (errorRegistry ? "\\nERR" : "\\nOK") + " \"" + registry + "\""; } } // Considerar os mecanismos na ordem crescente // de complexidade de processamento. ArrayList<Mechanism> mechanismListLocal = new ArrayList<Mechanism>(); mechanismListLocal.addAll(mechanismListIP); mechanismListLocal.addAll(mechanismListDNS); mechanismListLocal.addAll(mechanismListInclude); mechanismListLocal.addAll(mechanismListPTR); // Atribuio dos novos valores. this.mechanismList = mechanismListLocal; this.all = allLocal; this.redirect = redirectLocal; this.explanation = explanationLocal; this.error = errorQuery; CacheSPF.CHANGED = true; this.nxdomain = 0; updateLastRefresh(); Server.logLookupSPF(time, hostname, result); } } /** * Verifica se o whois um mecanismo all vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um mecanismo all vlido. */ private static boolean isMechanismAll(String token) { return Pattern.matches("^(\\+|-|~|\\?)?all$", token.toLowerCase()); } /** * Verifica se o whois um mecanismo ip4 vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um mecanismo ip4 vlido. */ private static boolean isMechanismIPv4(String token) { return Pattern.matches( "^((\\+|-|~|\\?)?ipv?4?:)?" + "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" + "(/[0-9]{1,2})?" + "$", token.toLowerCase()); } /** * Extrai um CIDR de IPv4 vlido. * * @param token o whois a ser verificado. * @return um CIDR de IPv4 vlido. */ private static String extractIPv4CIDR(String token) { Pattern pattern = Pattern.compile("(:|^)((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" + "(/[0-9]{1,2})?)$"); Matcher matcher = pattern.matcher(token.toLowerCase()); if (matcher.find()) { return matcher.group(2); } else { return null; } } /** * Verifica se o whois um mecanismo ip6 vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um mecanismo ip6 vlido. */ private static boolean isMechanismIPv6(String token) { return Pattern.matches("^((\\+|-|~|\\?)?ipv?6?:)?" + "((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|" + "(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|" + "((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|" + "(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:" + "((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|" + "(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|" + "((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|" + "((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|" + "((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|" + "((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|" + "(:(((:[0-9A-Fa-f]{1,4}){1,7})|" + "((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))" + "(%.+)?(\\/[0-9]{1,3})?" + "$", token); } /** * Extrai um CIDR de IPv6 vlido. * * @param token o whois a ser verificado. * @return um CIDR de IPv6 vlido. */ private static String extractIPv6CIDR(String token) { Pattern pattern = Pattern.compile("(:|^)(((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|" + "(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|" + "((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|" + "(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:" + "((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|" + "(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|" + "((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|" + "((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|" + "((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|" + "(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|" + "((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|" + "(:(((:[0-9A-Fa-f]{1,4}){1,7})|" + "((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)" + "(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))" + "(%.+)?(\\/[0-9]{1,3})?)$"); Matcher matcher = pattern.matcher(token); if (matcher.find()) { return matcher.group(2); } else { return null; } } private static String expand(String hostname, String ip, String sender, String helo) { int index = sender.indexOf('@'); String local = sender.substring(0, index); String domain = sender.substring(index + 1); hostname = hostname.replace("%{i}", ip); if (helo != null) { hostname = hostname.replace("%{h}", helo.startsWith(".") ? helo.substring(1) : helo); } hostname = hostname.replace("%{l}", local); hostname = hostname.replace("%{o}", domain); hostname = hostname.replace("%{d}", domain); hostname = hostname.replace("%{s}", sender.replace('@', '.')); hostname = hostname.replace("%{ir}", Subnet.reverse(ip)); return hostname; } /** * Verifica se o whois um mecanismo a vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um mecanismo a vlido. */ private static boolean isMechanismA(String token) { token = expand(token, "127.0.0.1", "sender@domain.tld", "host.domain.tld"); return Pattern.matches("^" + "(\\+|-|~|\\?)?a" + "(:(?=.{1,255}$)[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?(?:\\.[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?)*\\.?)?" + "(/[0-9]{1,2})?(//[0-9]{1,3})?" + "$", token.toLowerCase()); } /** * Verifica se o whois um mecanismo mx vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um mecanismo mx vlido. */ private static boolean isMechanismMX(String token) { token = expand(token, "127.0.0.1", "sender@domain.tld", "host.domain.tld"); return Pattern.matches("^(\\+|-|~|\\?)?mx" + "(:(?=.{1,255}$)[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?(?:\\.[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?)*\\.?)?" + "(\\.|/[0-9]{1,2})?(//[0-9]{1,3})?" + "$", token.toLowerCase()); } /** * Verifica se o whois um mecanismo ptr vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um mecanismo ptr vlido. */ private static boolean isMechanismPTR(String token) { token = expand(token, "127.0.0.1", "sender@domain.tld", "host.domain.tld"); return Pattern.matches("^(\\+|-|~|\\?)?ptr" + "(:(?=.{1,255}$)[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?(?:\\.[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?)*\\.?)?" + "$", token.toLowerCase()); } /** * Verifica se o whois um mecanismo existis vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um mecanismo existis vlido. */ private static boolean isMechanismExistis(String token) { token = expand(token, "127.0.0.1", "sender@domain.tld", "host.domain.tld"); return Pattern.matches("^(\\+|-|~|\\?)?exists:" + "((?=.{1,255}$)[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?(?:\\.[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?)*\\.?)" + "$", token.toLowerCase()); } /** * Verifica se o whois um mecanismo include vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um mecanismo include vlido. */ private static boolean isMechanismInclude(String token) { token = expand(token, "127.0.0.1", "sender@domain.tld", "host.domain.tld"); return Pattern.matches("^(\\+|-|~|\\?)?include:" + "(\\.?(?=.{1,255}$)[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?(?:\\.[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?)*\\.?)" + "$", token.toLowerCase()); } /** * Verifica se o whois um modificador redirect vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um modificador redirect vlido. */ private static boolean isModifierRedirect(String token) { token = expand(token, "127.0.0.1", "sender@domain.tld", "host.domain.tld"); return Pattern.matches("^redirect=" + "((?=.{1,255}$)[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?(?:\\.[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?)*\\.?)" + "$", token.toLowerCase()); } /** * Verifica se o whois um modificador explanation vlido. * * @param token o whois a ser verificado. * @return verdadeiro se o whois um modificador explanation vlido. */ private static boolean isModifierExplanation(String token) { token = expand(token, "127.0.0.1", "sender@domain.tld", "host.domain.tld"); return Pattern.matches("^exp=" + "((?=.{1,255}$)[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?(?:\\.[0-9A-Za-z_](?:(?:[0-9A-Za-z_]|-){0,61}[0-9A-Za-z_])?)*\\.?)" + "$", token.toLowerCase()); } /** * Verifica se o registro atual expirou. * * @return verdadeiro se o registro atual expirou. */ public boolean isRegistryExpired() { long expiredTime = (System.currentTimeMillis() - lastRefresh) / Server.DAY_TIME; return expiredTime > REFRESH_TIME; } /** * Verifica se o registro atual expirou. * * @return verdadeiro se o registro atual expirou. */ public boolean isRegistryExpired7() { long expiredTime = (System.currentTimeMillis() - lastRefresh) / Server.DAY_TIME * 7; return expiredTime > REFRESH_TIME; } /** * Verifica se o registro atual expirou. * * @return verdadeiro se o registro atual expirou. */ public boolean isRegistryExpired14() { long expiredTime = (int) (System.currentTimeMillis() - lastRefresh) / Server.DAY_TIME * 14; return expiredTime > REFRESH_TIME; } /** * Retorna o resultado SPF para um IP especifico. * * @param ip o IP a ser verificado. * @return o resultado SPF para um IP especifico. * @throws ProcessException se houver falha no processamento. */ public String getResult(String ip, String sender, String helo, LinkedList<String> logList) throws ProcessException { Qualifier qualifier = getQualifier(ip, sender, helo, 0, new TreeSet<String>(), logList); if (qualifier == null) { return "NONE"; } else { return qualifier.name(); } } private void logRedirect(String redirect, Qualifier qualifier, LinkedList<String> logList) { if (logList != null) { logList.add(getHostname() + ":redirect:" + redirect + " => " + (qualifier == null ? "NOT MATCH" : qualifier.name())); } } private void logRedirect(String redirect, String message, LinkedList<String> logList) { if (logList != null) { logList.add(getHostname() + ":redirect:" + redirect + " => " + message); } } private void logError(Qualifier qualifier, LinkedList<String> logList) { if (logList != null) { logList.add(getHostname() + ":error => " + (qualifier == null ? "NOT MATCH" : qualifier.name())); } } private void logAll(Qualifier qualifier, LinkedList<String> logList) { if (logList != null) { logList.add(getHostname() + ":all => " + (qualifier == null ? "NOT MATCH" : qualifier.name())); } } private void logMechanism(Mechanism mechanism, Qualifier qualifier, LinkedList<String> logList) { if (logList != null) { logList.add(getHostname() + ":" + mechanism + " => " + (qualifier == null ? "NOT MATCH" : qualifier.name())); } } /** * Retorna o qualificador para uma consulta SPF. * * @param ip o IP a ser verificado. * @param deep a profundiade de navegao da vore SPF. * @param hostVisitedSet o conjunto de hosts visitados. * @return o qualificador da consulta SPF. * @throws ProcessException se houver falha no processamento. */ private Qualifier getQualifier(String ip, String sender, String helo, int deep, TreeSet<String> hostVisitedSet, LinkedList<String> logList) throws ProcessException { if (deep > 10) { return null; // Evita excesso de consultas. } else if (hostVisitedSet.contains(getHostname())) { return null; // Evita looping infinito. } else if (mechanismList == null) { throw new ProcessException("ERROR: HOST NOT FOUND"); } else { boolean hostNotFound = false; hostVisitedSet.add(getHostname()); for (Mechanism mechanism : mechanismList) { if (mechanism instanceof MechanismInclude) { try { MechanismInclude include = (MechanismInclude) mechanism; Qualifier qualifier = include.getQualifierSPF(ip, sender, helo, deep + 1, hostVisitedSet, logList); if (qualifier == null) { // Nenhum qualificador foi definido // ento continuar busca. } else { return qualifier; } } catch (ProcessException ex) { if (ex.getMessage().equals("ERROR: HOST NOT FOUND")) { // No foi possvel fazer o include. // O hostname mencionado no existe. // Continuar a verificao dos demais // mecanismos antes de efetivar o erro. hostNotFound = true; } else { throw ex; } } } else if (mechanism instanceof MechanismPTR) { if (mechanism.match(ip, sender, helo)) { // Mecanismo PTR s ser processado // no primeiro nvel da rvore. Qualifier qualifier = mechanism.getQualifier(); logMechanism(mechanism, qualifier, logList); return qualifier; } else { logMechanism(mechanism, null, logList); } } else if (mechanism.match(ip, sender, helo)) { Qualifier qualifier = mechanism.getQualifier(); logMechanism(mechanism, qualifier, logList); return qualifier; } else { logMechanism(mechanism, null, logList); } } if (redirect != null) { SPF spf = CacheSPF.get(redirect); if (spf == null) { logRedirect(redirect, "NOT FOUND", logList); return null; } else { Qualifier qualifier = spf.getQualifier(ip, sender, helo, 0, hostVisitedSet, logList); logRedirect(redirect, qualifier, logList); return qualifier; } } else if (error || hostNotFound) { // Nova interpretao SPF para erro de sintaxe. // Em caso de erro, retornar SOFTFAIL. logError(Qualifier.SOFTFAIL, logList); return Qualifier.SOFTFAIL; } else if (deep > 0) { // O mecanismo all s deve ser // processado no primeiro nvel da rvore. return null; } else { // Retorna o qualificador do mecanismo all. // Pode ser nulo caso o registro no apresente o mecanismo all. logAll(all, logList); return all; } } } /** * Retorna o hostname do registro SPF. * * @return o hostname do registro SPF. */ public String getHostname() { return hostname; } /** * Retorna o dominio de explicao do registro SPF. No sei ainda do que se * trata. * * @return o dominio de explicao do registro SPF. */ public String getExplanation() { return explanation; } /** * A enumerao que representa todos os qualificadores possveis. */ public enum Qualifier { PASS("Pass"), FAIL("Fail"), SOFTFAIL("SoftFail"), NEUTRAL("Neutral"); private final String description; private Qualifier(String description) { this.description = description; } public static Qualifier get(String name) { try { return valueOf(name); } catch (Exception ex) { return null; } } public static String name(Qualifier qualifier) { if (qualifier == null) { return null; } else { return qualifier.name(); } } @Override public String toString() { return description; } } /** * Classe abstrata que representa qualquer mecanismo de processamento SPF. */ private abstract class Mechanism implements Serializable { private static final long serialVersionUID = 1L; private final String expression; private final Qualifier qualifier; private Mechanism(String expression) { this.expression = expression; switch (expression.charAt(0)) { case '+': this.qualifier = Qualifier.PASS; break; case '-': this.qualifier = Qualifier.FAIL; break; case '~': this.qualifier = Qualifier.SOFTFAIL; break; case '?': this.qualifier = Qualifier.NEUTRAL; break; default: this.qualifier = Qualifier.PASS; // Default qualifier. } } public abstract boolean match(String ip, String sender, String helo) throws ProcessException; public Qualifier getQualifier() { return qualifier; } public String getExpression() { return expression; } public boolean equals(Mechanism other) { if (other == null) { return false; } else if (this.qualifier != other.qualifier) { return false; } else { return this.expression.equals(other.expression); } } @Override public String toString() { return expression; } } /** * Mecanismo de processamento CIDR de IPv4. */ private final class MechanismIPv4 extends Mechanism { private static final long serialVersionUID = 1L; private final int address; private final int mask; /** * Marcado sempre que o mecanismo aponta para blocos reservados. */ private final boolean reserved; public MechanismIPv4(String expression) { super(expression); int index = expression.indexOf(':'); String inetnum = expression.substring(index + 1); index = inetnum.indexOf('/'); int addressLocal; int maskLocal; if (index == -1) { maskLocal = 0xFFFFFFFF; addressLocal = SubnetIPv4.getAddressIP(inetnum); } else { maskLocal = SubnetIPv4.getMaskNet(inetnum.substring(index + 1)); addressLocal = SubnetIPv4.getAddressIP(inetnum.substring(0, index)) & maskLocal; } // Verifica se o endereo pertence a blocos reservados. boolean reservedLocal = addressLocal == 0xFFFFFFFF; // Broadcast reservedLocal = reservedLocal || (addressLocal & 0xFF000000) == 0x00000000; // RFC 1700 Rede corrente reservedLocal = reservedLocal || (addressLocal & 0xFF000000) == 0x0A000000; // RFC 1918 Rede Privada reservedLocal = reservedLocal || (addressLocal & 0xFF000000) == 0x0E000000; // RFC 1700 Rede Pblica reservedLocal = reservedLocal || (addressLocal & 0xFF000000) == 0x27000000; // RFC 1797 Reservado reservedLocal = reservedLocal || (addressLocal & 0xFF000000) == 0x7F000000; // RFC 3330 Localhost reservedLocal = reservedLocal || (addressLocal & 0xFFFF0000) == 0x80000000; // RFC 3330 Reservado (IANA) reservedLocal = reservedLocal || (addressLocal & 0xFFFF0000) == 0xA9FE0000; // RFC 3927 Zeroconf reservedLocal = reservedLocal || (addressLocal & 0xFFF00000) == 0xAC100000; // RFC 1918 Rede privada reservedLocal = reservedLocal || (addressLocal & 0xFFFF0000) == 0xBFFF0000; // RFC 3330 Reservado (IANA) reservedLocal = reservedLocal || (addressLocal & 0xFFFFFF00) == 0xC0000200; // RFC 3330 Documentao reservedLocal = reservedLocal || (addressLocal & 0xFFFFFF00) == 0xC0586300; // RFC 3068 IPv6 para IPv4 reservedLocal = reservedLocal || (addressLocal & 0xFFFF0000) == 0xC0A80000; // RFC 1918 Rede Privada reservedLocal = reservedLocal || (addressLocal & 0xFFFE0000) == 0xC6120000; // RFC 2544 Teste de benchmark de redes reservedLocal = reservedLocal || (addressLocal & 0xFFFFFF00) == 0xDFFFFF00; // RFC 3330 Reservado reservedLocal = reservedLocal || (addressLocal & 0xF0000000) == 0xE0000000; // RFC 3171 Multicasts (antiga rede Classe D) reservedLocal = reservedLocal || (addressLocal & 0xF0000000) == 0xF0000000; // RFC 1700 Reservado (antiga rede Classe E) // Verifica se algum endereo reservado pertence ao bloco do mecanismo. reservedLocal = reservedLocal || addressLocal == (0x0A000000 & maskLocal); // RFC 1918 Rede Privada reservedLocal = reservedLocal || addressLocal == (0x0E000000 & maskLocal); // RFC 1700 Rede Pblica reservedLocal = reservedLocal || addressLocal == (0x27000000 & maskLocal); // RFC 1797 Reservado reservedLocal = reservedLocal || addressLocal == (0x7F000000 & maskLocal); // RFC 3330 Localhost reservedLocal = reservedLocal || addressLocal == (0x80000000 & maskLocal); // RFC 3330 Reservado (IANA) reservedLocal = reservedLocal || addressLocal == (0xA9FE0000 & maskLocal); // RFC 3927 Zeroconf reservedLocal = reservedLocal || addressLocal == (0xAC100000 & maskLocal); // RFC 1918 Rede privada reservedLocal = reservedLocal || addressLocal == (0xBFFF0000 & maskLocal); // RFC 3330 Reservado (IANA) reservedLocal = reservedLocal || addressLocal == (0xC0000200 & maskLocal); // RFC 3330 Documentao reservedLocal = reservedLocal || addressLocal == (0xC0586300 & maskLocal); // RFC 3068 IPv6 para IPv4 reservedLocal = reservedLocal || addressLocal == (0xC0A80000 & maskLocal); // RFC 1918 Rede Privada reservedLocal = reservedLocal || addressLocal == (0xC6120000 & maskLocal); // RFC 2544 Teste de benchmark de redes reservedLocal = reservedLocal || addressLocal == (0xDFFFFF00 & maskLocal); // RFC 3330 Reservado reservedLocal = reservedLocal || addressLocal == (0xE0000000 & maskLocal); // RFC 3171 Multicasts (antiga rede Classe D) reservedLocal = reservedLocal || addressLocal == (0xF0000000 & maskLocal); // RFC 1700 Reservado (antiga rede Classe E) if (reservedLocal) { Server.logDebug("SPF mecanism reserved: " + expression); } // Associao dos atributos. this.address = addressLocal; this.mask = maskLocal; this.reserved = reservedLocal; } public boolean isReserved() { return reserved; } @Override public boolean match(String ip, String sender, String helo) { if (isReserved()) { // Sempre que estiver apontando para // blocos reservados, ignorar o mecanismo. return false; } else if (SubnetIPv4.isValidIPv4(ip)) { int address2 = SubnetIPv4.getAddressIP(ip); return address == (address2 & mask); } else { return false; } } } /** * Mecanismo de processamento CIDR de IPv6. */ private final class MechanismIPv6 extends Mechanism { private static final long serialVersionUID = 1L; private final short[] address; private final short[] mask; public MechanismIPv6(String expression) { super(expression); String inetnum; int index = expression.indexOf(':'); try { String first = expression.substring(0, index); Integer.parseInt(first, 16); inetnum = expression; } catch (NumberFormatException ex) { inetnum = expression.substring(index + 1); } index = inetnum.indexOf('/'); if (index == -1) { this.mask = SubnetIPv6.getMaskIPv6(128); this.address = SubnetIPv6.split(inetnum); } else { this.mask = SubnetIPv6.getMaskIPv6(inetnum.substring(index + 1)); this.address = SubnetIPv6.split(inetnum.substring(0, index), mask); } } @Override public boolean match(String ip, String sender, String helo) { if (SubnetIPv6.isValidIPv6(ip)) { short[] address2 = SubnetIPv6.split(ip); for (int i = 0; i < 8; i++) { if (address[i] != (address2[i] & mask[i])) { return false; } } return true; } else { return false; } } } /** * Mecanismo de processamento do registro A. */ private final class MechanismA extends Mechanism { private static final long serialVersionUID = 1L; private final ArrayList<Mechanism> mechanismList = new ArrayList<Mechanism>(); private boolean loaded = false; public MechanismA(String expression, boolean load) { super(expression); if (load && !expression.contains("%")) { loadList("127.0.0.1", "sender@domain.tld", "host.domain.tld"); } } private String getExpression(String ip, String sender, String helo) { String expression = getExpression(); expression = expand(expression, ip, sender, helo); if (!Character.isLetter(expression.charAt(0))) { // Expresso com qualificador. // Extrair qualificador. expression = expression.substring(1); } if (expression.startsWith("a:")) { expression = expression.substring(2); } else if (expression.startsWith("a")) { expression = SPF.this.getHostname() + expression.substring(1); } return expression; } private synchronized void loadList(String ip, String sender, String helo) { if (!loaded) { long time = System.currentTimeMillis(); // Carregamento de lista. String expression = getExpression(ip, sender, helo); String hostname = expression; String maskIPv4 = null; String maskIPv6 = null; int indexIPv6Prefix = hostname.indexOf("//"); if (indexIPv6Prefix != -1) { maskIPv6 = hostname.substring(indexIPv6Prefix + 2); hostname = hostname.substring(0, indexIPv6Prefix); } int indexIPv4Prefix = hostname.indexOf('/'); if (indexIPv4Prefix != -1) { maskIPv4 = hostname.substring(indexIPv4Prefix + 1); hostname = hostname.substring(0, indexIPv4Prefix); } try { TreeSet<String> resultSet = new TreeSet<String>(); Attributes attributes = Server.getAttributesDNS(hostname, new String[] { "A" }); Attribute attributeA = attributes.get("A"); if (attributeA != null) { NamingEnumeration enumeration = attributeA.getAll(); while (enumeration.hasMoreElements()) { String hostAddress = (String) enumeration.next(); int indexSpace = hostAddress.indexOf(' ') + 1; hostAddress = hostAddress.substring(indexSpace); if (!SubnetIPv4.isValidIPv4(hostAddress)) { try { hostAddress = InetAddress.getByName(hostAddress).getHostAddress(); } catch (UnknownHostException ex) { // Registro A no encontrado. } } if (maskIPv4 != null) { hostAddress += "/" + maskIPv4; } mechanismList.add(new MechanismIPv4(hostAddress)); resultSet.add(hostAddress); } } attributes = Server.getAttributesDNS(hostname, new String[] { "AAAA" }); Attribute attributeAAAA = attributes.get("AAAA"); if (attributeAAAA != null) { NamingEnumeration enumeration = attributeAAAA.getAll(); while (enumeration.hasMoreElements()) { String hostAddress = (String) enumeration.next(); int indexSpace = hostAddress.indexOf(' ') + 1; hostAddress = hostAddress.substring(indexSpace); if (Domain.isHostname(hostAddress)) { try { hostAddress = Inet6Address.getByName(hostAddress).getHostAddress(); } catch (UnknownHostException ex) { // Registro AAAA no encontrado. hostAddress = null; } } if (SubnetIPv6.isValidIPv6(hostAddress)) { if (maskIPv6 != null) { hostAddress += "/" + maskIPv6; } mechanismList.add(new MechanismIPv6(hostAddress)); resultSet.add(hostAddress); } } } Server.logMecanismA(time, expression, resultSet.toString()); } catch (CommunicationException ex) { Server.logMecanismA(time, expression, "TIMEOUT"); } catch (ServiceUnavailableException ex) { Server.logMecanismA(time, expression, "SERVFAIL"); } catch (NameNotFoundException ex) { Server.logMecanismA(time, expression, "NOT FOUND"); } catch (InvalidAttributeIdentifierException ex) { Server.logMecanismA(time, expression, "NOT FOUND"); } catch (InvalidNameException ex) { Server.logMecanismA(time, expression, "INVALID"); } catch (NamingException ex) { Server.logMecanismA(time, expression, "ERROR " + ex.getClass() + " " + ex.getMessage()); } if (!expression.contains("%")) { loaded = true; } } } @Override public boolean match(String ip, String sender, String helo) throws ProcessException { loadList(ip, sender, helo); for (Mechanism mechanism : mechanismList) { if (mechanism.match(ip, sender, helo)) { return true; } } return false; } } /** * Mecanismo de processamento do registro MX. */ private final class MechanismMX extends Mechanism { private static final long serialVersionUID = 1L; private final ArrayList<Mechanism> mechanismList = new ArrayList<Mechanism>(); private boolean loaded = false; public MechanismMX(String expression, boolean load) { super(expression); if (load && !expression.contains("%")) { loadList("127.0.0.1", "sender@domain.tld", "host.domain.tld"); } } private String getExpression(String ip, String sender, String helo) { String expression = getExpression(); expression = expand(expression, ip, sender, helo); if (!Character.isLetter(expression.charAt(0))) { // Expresso com qualificador. // Extrair qualificador. expression = expression.substring(1); } if (expression.startsWith("mx:")) { expression = expression.substring(3); } else if (expression.startsWith("mx")) { expression = SPF.this.getHostname() + expression.substring(2); } return expression; } private synchronized void loadList(String ip, String sender, String helo) { if (!loaded) { long time = System.currentTimeMillis(); mechanismList.clear(); // Carregamento de lista. String expression = getExpression(ip, sender, helo); String hostname = expression; String maskIPv4 = null; String maskIPv6 = null; int indexIPv6Prefix = hostname.indexOf("//"); if (indexIPv6Prefix != -1) { maskIPv6 = hostname.substring(indexIPv6Prefix + 2); hostname = hostname.substring(0, indexIPv6Prefix); } int indexIPv4Prefix = hostname.indexOf('/'); if (indexIPv4Prefix != -1) { maskIPv4 = hostname.substring(indexIPv4Prefix + 1); hostname = hostname.substring(0, indexIPv4Prefix); } try { TreeSet<String> resultSet = new TreeSet<String>(); Attributes attributesMX = Server.getAttributesDNS(hostname, new String[] { "MX" }); Attribute attributeMX = attributesMX.get("MX"); if (attributeMX == null) { Attributes attributesA = Server.getAttributesDNS(hostname, new String[] { "A" }); Attribute attributeA = attributesA.get("A"); if (attributeA != null) { for (int i = 0; i < attributeA.size(); i++) { String host4Address = (String) attributeA.get(i); if (SubnetIPv4.isValidIPv4(host4Address)) { if (maskIPv4 != null) { host4Address += "/" + maskIPv4; } mechanismList.add(new MechanismIPv4(host4Address)); resultSet.add(host4Address); } } } Attributes attributesAAAA = Server.getAttributesDNS(hostname, new String[] { "AAAA" }); Attribute attributeAAAA = attributesAAAA.get("AAAA"); if (attributeAAAA != null) { for (int i = 0; i < attributeAAAA.size(); i++) { String host6Address = (String) attributeAAAA.get(i); if (SubnetIPv6.isValidIPv6(host6Address)) { if (maskIPv6 != null) { host6Address += "/" + maskIPv6; } mechanismList.add(new MechanismIPv6(host6Address)); resultSet.add(host6Address); } } } } else { NamingEnumeration enumeration = attributeMX.getAll(); while (enumeration.hasMoreElements()) { String hostAddress = (String) enumeration.next(); int indexSpace = hostAddress.indexOf(' ') + 1; hostAddress = hostAddress.substring(indexSpace); if (SubnetIPv4.isValidIPv4(hostAddress)) { if (maskIPv4 != null) { hostAddress += "/" + maskIPv4; } mechanismList.add(new MechanismIPv4(hostAddress)); resultSet.add(hostAddress); } else if (SubnetIPv6.isValidIPv6(hostAddress)) { if (maskIPv6 != null) { hostAddress += "/" + maskIPv6; } mechanismList.add(new MechanismIPv6(hostAddress)); resultSet.add(hostAddress); } else { try { Attributes attributesA = Server.getAttributesDNS(hostAddress, new String[] { "A" }); Attribute attributeA = attributesA.get("A"); if (attributeA != null) { for (int i = 0; i < attributeA.size(); i++) { String host4Address = (String) attributeA.get(i); if (SubnetIPv4.isValidIPv4(host4Address)) { if (maskIPv4 != null) { host4Address += "/" + maskIPv4; } mechanismList.add(new MechanismIPv4(host4Address)); resultSet.add(host4Address); } } } Attributes attributesAAAA = Server.getAttributesDNS(hostAddress, new String[] { "AAAA" }); Attribute attributeAAAA = attributesAAAA.get("AAAA"); if (attributeAAAA != null) { for (int i = 0; i < attributeAAAA.size(); i++) { String host6Address = (String) attributeAAAA.get(i); if (SubnetIPv6.isValidIPv6(host6Address)) { if (maskIPv6 != null) { host6Address += "/" + maskIPv6; } mechanismList.add(new MechanismIPv6(host6Address)); resultSet.add(host6Address); } } } } catch (NamingException ex) { // Endereo no encontrado. } } } } Server.logMecanismMX(time, expression, resultSet.toString()); } catch (CommunicationException ex) { Server.logMecanismMX(time, expression, "TIMEOUT"); } catch (ServiceUnavailableException ex) { Server.logMecanismMX(time, expression, "SERVFAIL"); } catch (NameNotFoundException ex) { Server.logMecanismMX(time, expression, "NOT FOUND"); } catch (InvalidAttributeIdentifierException ex) { Server.logMecanismMX(time, expression, "NOT FOUND"); } catch (InvalidNameException ex) { Server.logMecanismA(time, expression, "INVALID"); } catch (NamingException ex) { Server.logMecanismMX(time, expression, "ERROR " + ex.getClass() + " " + ex.getMessage()); } if (!getExpression().contains("%")) { loaded = true; } } } @Override public boolean match(String ip, String sender, String helo) throws ProcessException { loadList(ip, sender, helo); for (Mechanism mechanism : mechanismList) { if (mechanism.match(ip, sender, helo)) { return true; } } return false; } } /** * Mecanismo de processamento do reverso do IP de origem. */ private final class MechanismPTR extends Mechanism { private static final long serialVersionUID = 1L; public MechanismPTR(String expression) { super(expression); } private String getHostname(String ip, String sender, String helo) { String expression = getExpression(); int index = expression.indexOf(':'); if (index == -1) { return SPF.this.getHostname(); } else { expression = expression.substring(index + 1); expression = expand(expression, ip, sender, helo); return expression; } } @Override public synchronized boolean match(String ip, String sender, String helo) throws ProcessException { String hostname = getHostname(ip, sender, helo); int index = hostname.indexOf(':'); if (index > 0) { hostname = "." + hostname.substring(index + 1); } else { hostname = "." + hostname; } Reverse reverse = Reverse.get(ip); if (reverse == null) { return false; } else { for (String address : reverse.getAddressSet()) { if (address.endsWith(hostname)) { return true; } } return false; } } } /** * Mecanismo de processamento exists. */ private final class MechanismExists extends Mechanism { private static final long serialVersionUID = 1L; public MechanismExists(String expression) { super(expression); } private String getHostname(String ip, String sender, String helo) { String expression = getExpression(); int index = expression.indexOf(':') + 1; expression = expression.substring(index); expression = expand(expression, ip, sender, helo); return expression; } @Override public boolean match(String ip, String sender, String helo) throws ProcessException { long time = System.currentTimeMillis(); String hostname = getHostname(ip, sender, helo); try { Server.getAttributesDNS(hostname, new String[] { "A" }); Server.logMecanismA(time, hostname, "EXISTS"); return true; } catch (CommunicationException ex) { Server.logMecanismA(time, hostname, "TIMEOUT"); return false; } catch (ServiceUnavailableException ex) { Server.logMecanismA(time, hostname, "SERVFAIL"); return false; } catch (NameNotFoundException ex) { Server.logMecanismA(time, hostname, "NOT FOUND"); return false; } catch (InvalidAttributeIdentifierException ex) { Server.logMecanismA(time, hostname, "NOT FOUND"); return false; } catch (InvalidNameException ex) { Server.logMecanismA(time, hostname, "INVALID"); return false; } catch (NamingException ex) { Server.logMecanismA(time, hostname, "ERROR " + ex.getClass() + " " + ex.getMessage()); return false; } } } /** * Mecanismo de incluso de um n na rvore SPF. */ private final class MechanismInclude extends Mechanism { private static final long serialVersionUID = 1L; public MechanismInclude(String expression) { super(expression); } private String getHostname(String ip, String sender, String helo) { String expression = getExpression(); int index = expression.indexOf(':') + 1; expression = expression.substring(index); expression = expand(expression, ip, sender, helo); return expression; } private Qualifier getQualifierSPF(String ip, String sender, String helo, int deep, TreeSet<String> hostVisitedSet, LinkedList<String> logList) throws ProcessException { String hostname = getHostname(ip, sender, helo); SPF spf = CacheSPF.get(hostname); if (spf == null) { return null; } else { return spf.getQualifier(ip, sender, helo, deep, hostVisitedSet, logList); } } @Override public boolean match(String ip, String sender, String helo) throws ProcessException { throw new ProcessException("ERROR: FATAL ERROR"); // No pode fazer o match direto. } } @Override public String toString() { return hostname + " " + mechanismList + " " + redirect + " " + all; } /** * Classe que representa o cache de registros SPF. */ private static class CacheSPF { /** * Mapa para cache dos registros SPF consultados. */ private static final HashMap<String, SPF> MAP = new HashMap<String, SPF>(); /** * O prximo registro SPF que deve ser atualizado. */ private static SPF spfRefresh = null; /** * Flag que indica se o cache foi modificado. */ private static boolean CHANGED = false; private static boolean isChanged() { return CHANGED; } private static void setNotChanged() { CHANGED = false; } private static synchronized SPF dropExact(String token) { SPF ret = MAP.remove(token); if (ret != null) { CHANGED = true; } return ret; } private static synchronized SPF putExact(String key, SPF value) { SPF ret = MAP.put(key, value); if (!value.equals(ret)) { CHANGED = true; } return ret; } private static synchronized TreeSet<String> keySet() { TreeSet<String> keySet = new TreeSet<String>(); keySet.addAll(MAP.keySet()); return keySet; } private static synchronized HashMap<String, SPF> getMap() { HashMap<String, SPF> map = new HashMap<String, SPF>(); map.putAll(MAP); return map; } private static SPF getExact(String host) { return MAP.get(host); } private static synchronized Collection<SPF> getValues() { return MAP.values(); } private static synchronized SPF getRefreshSPF() { SPF spf = spfRefresh; spfRefresh = null; return spf; } private static synchronized void addQuerie(SPF spf) { spf.queries++; if (spfRefresh == null) { spfRefresh = spf; } else if (spfRefresh.queries < spf.queries) { spfRefresh = spf; } } private static void dropExpired() { for (String host : keySet()) { long time = System.currentTimeMillis(); SPF spf = getExact(host); if (spf != null && spf.isRegistryExpired14()) { spf = dropExact(host); if (spf != null) { Server.logLookupSPF(time, host, "EXPIRED"); } } } } /** * Adiciona um registro SPF no mapa de cache. * * @param spf o registro SPF para ser adocionado. */ private static void add(SPF spf) { putExact(spf.getHostname(), spf); } private static boolean refresh(String address, boolean load) throws ProcessException { String host = Domain.extractHost(address, false); if (host == null) { return false; } else { SPF spf = getExact(host); if (spf == null) { if (load) { spf = new SPF(host); add(spf); return true; } else { return false; } } else { spf.refresh(load, false); return true; } } } private static SPF get(String address) throws ProcessException { return get(address, false); } /** * Retorna o registro SPF do e-mail. * * @param address o endereo de e-mail que deve ser consultado. * @return o registro SPF, se for encontrado. * @throws ProcessException se houver falha no processamento. */ private static SPF get(String address, boolean refresh) throws ProcessException { String host = Domain.extractHost(address, false); if (host == null) { return null; } else { SPF spf = getExact(host); if (spf == null) { spf = new SPF(host); add(spf); } else if (refresh || spf.isRegistryExpired()) { try { // Atualiza o registro se ele for antigo demais. spf.refresh(false, false); } catch (ProcessException ex) { if (ex.getMessage().equals("ERROR: DNS UNAVAILABLE")) { // Manter registro anterior quando houver erro de DNS. Server.logDebug(address + ": SPF temporarily unavailable."); } else { throw ex; } } } // spf.queries++; // Incrementa o contador de consultas. addQuerie(spf); // Incrementa o contador de consultas. return spf; } } private static void store() { if (isChanged()) { try { // Server.logTrace("storing spf.map"); long time = System.currentTimeMillis(); File file = new File("./data/spf.map"); HashMap<String, SPF> map = getMap(); FileOutputStream outputStream = new FileOutputStream(file); try { SerializationUtils.serialize(map, outputStream); setNotChanged(); } finally { outputStream.close(); } Server.logStore(time, file); } catch (Exception ex) { Server.logError(ex); } } } private static void load() { long time = System.currentTimeMillis(); File file = new File("./data/spf.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 SPF) { SPF spf = (SPF) value; if (!spf.isRegistryExpired14()) { putExact(key, spf); } } } setNotChanged(); Server.logLoad(time, file); } catch (Exception ex) { Server.logError(ex); } } } /** * Atualiza o registro mais consultado. */ private static void refresh() { SPF spfMax = getRefreshSPF(); if (spfMax == null) { for (SPF spf : getValues()) { if (spfMax == null) { spfMax = spf; } else if (spfMax.queries < spf.queries) { spfMax = spf; } } } if (spfMax != null && spfMax.queries > 3) { try { spfMax.refresh(true, false); } catch (ProcessException ex) { spfMax.updateLastRefresh(); if (ex.getMessage().equals("ERROR: HOST NOT FOUND")) { Server.logDebug(spfMax.getHostname() + ": SPF registry cache removed."); } else if (ex.getMessage().equals("ERROR: DNS UNAVAILABLE")) { // Manter registro anterior quando houver erro de DNS. Server.logDebug(spfMax.getHostname() + ": SPF temporarily unavailable."); } else { Server.logError(ex); } } } } } public static Qualifier getQualifier(String ip, String sender, String helo, boolean refresh) throws ProcessException { SPF spf = CacheSPF.get(sender, refresh); return spf.getQualifier(ip, sender, helo, 0, new TreeSet<String>(), null); } public static Qualifier getQualifier2(String ip, String sender, String helo, boolean refresh) { try { SPF spf = CacheSPF.get(sender, refresh); return spf.getQualifier(ip, sender, helo, 0, new TreeSet<String>(), null); } catch (ProcessException ex) { return null; } } public static void dropExpiredSPF() { CacheSPF.dropExpired(); } public static void refreshSPF() { CacheSPF.refresh(); } public static void refreshHELO() { CacheHELO.refresh(); } /** * Adiciona uma nova reclamao de SPAM. * * @param ticket o ticket da mensagem original. * @throws ProcessException se houver falha no processamento do ticket. */ public static TreeSet<String> addComplain(String origin, String ticket) throws ProcessException { if (ticket == null) { return null; } else { long time = System.currentTimeMillis(); TreeSet<String> tokenSet = new TreeSet<String>(); TreeSet<String> blackSet = new TreeSet<String>(); String registry = Server.decrypt(ticket); int index = registry.indexOf(' '); Date date = getTicketDate(registry.substring(0, index)); if (System.currentTimeMillis() - date.getTime() > 432000000) { // Ticket vencido com mais de 5 dias. throw new ProcessException("TICKET EXPIRED"); } else { registry = registry.substring(index + 1); StringTokenizer tokenizer = new StringTokenizer(registry, " "); StringBuilder builder = new StringBuilder(); String recipient = null; while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (isValidReputation(token)) { tokenSet.add(token); if (builder.length() > 0) { builder.append(' '); } builder.append(token); } else if (token.startsWith(">") && Domain.isEmail(token.substring(1))) { recipient = token.substring(1); } } for (String key : expandTokenSet(tokenSet)) { Distribution distribution = CacheDistribution.get(key, true); if (Ignore.contains(key)) { distribution.addHam(date.getTime()); } else if (distribution.addSpam(date.getTime())) { distribution.getStatus(key); Peer.sendToAll(key, distribution); } blackSet.add(key); } Server.log(time, Core.Level.DEBUG, "CMPLN", origin, ticket, blackSet, recipient); return blackSet; } } } public static long getDateTicket(String ticket) throws ProcessException { try { byte[] byteArray = Server.decryptToByteArrayURLSafe(ticket); if (byteArray.length > 8) { long date = byteArray[7] & 0xFF; date <<= 8; date += byteArray[6] & 0xFF; date <<= 8; date += byteArray[5] & 0xFF; date <<= 8; date += byteArray[4] & 0xFF; date <<= 8; date += byteArray[3] & 0xFF; date <<= 8; date += byteArray[2] & 0xFF; date <<= 8; date += byteArray[1] & 0xFF; date <<= 8; date += byteArray[0] & 0xFF; return date; } else { return 0; } } catch (ProcessException ex) { return 0; } } private static String getHoldStatus(Client client, long time, User.Query query) { if (query.isWhiteSender()) { SPF.setHam(time, query.getTokenSet()); query.setResult("WHITE"); return "WHITE"; } else if (query.isBlockSender()) { SPF.setSpam(time, query.getTokenSet()); return "REMOVE"; } else if (query.isInexistent(client)) { query.setResult("INEXISTENT"); return "REMOVE"; } else if (User.isExpiredHOLD(time)) { Server.logTrace("query expired."); if (query.isRecipientAdvised()) { query.blockSender(time); query.setResult("BLOCK"); } else if (query.isSenderAdvised() && query.isSenderRed()) { query.blockSender(time); query.setResult("BLOCK"); } else { query.setResult("REJECT"); } return "REMOVE"; } else if (query.isAnyLinkBLOCK()) { Action action = client == null ? Action.REJECT : client.getActionBLOCK(); if (action == Action.FLAG) { query.setResult("FLAG"); return "FLAG"; } else if (action == Action.HOLD) { query.setResult("HOLD"); return "HOLD"; } else { SPF.setSpam(time, query.getTokenSet()); query.blockSender(time); query.setResult("REJECT"); return "REMOVE"; } } else if (query.isAnyLinkRED()) { Action action = client == null ? Action.FLAG : client.getActionRED(); if (action == Action.FLAG) { query.setResult("FLAG"); return "FLAG"; } else if (action == Action.HOLD) { query.setResult("HOLD"); return "HOLD"; } else { SPF.setSpam(time, query.getTokenSet()); query.setResult("REJECT"); return "REMOVE"; } } else if (query.hasTokenRed() || query.hasClusterRed()) { return "HOLD"; } else if (query.hasYellow()) { long lifeTimeMin = (System.currentTimeMillis() - time) / 1000 / 60; if (lifeTimeMin < Core.getDeferTimeYELLOW()) { return "HOLD"; } else { SPF.setHam(time, query.getTokenSet()); query.setResult("ACCEPT"); return "ACCEPT"; } } else if (query.isSoftfail()) { long lifeTimeMin = (System.currentTimeMillis() - time) / 1000 / 60; if (lifeTimeMin < Core.getDeferTimeSOFTFAIL()) { return "HOLD"; } else { SPF.setHam(time, query.getTokenSet()); query.setResult("ACCEPT"); return "ACCEPT"; } } else if (query.isGreen()) { SPF.setHam(time, query.getTokenSet()); query.setResult("ACCEPT"); return "ACCEPT"; } else if (query.isResult("HOLD")) { return "HOLD"; } else { return "REMOVE"; } } private static String getHoldStatus(Client client, String ticket, LinkedList<User> userList) { if (ticket == null) { return "INVALID"; } else { try { byte[] byteArray = Server.decryptToByteArrayURLSafe(ticket); if (byteArray.length > 8) { long date = byteArray[7] & 0xFF; date <<= 8; date += byteArray[6] & 0xFF; date <<= 8; date += byteArray[5] & 0xFF; date <<= 8; date += byteArray[4] & 0xFF; date <<= 8; date += byteArray[3] & 0xFF; date <<= 8; date += byteArray[2] & 0xFF; date <<= 8; date += byteArray[1] & 0xFF; date <<= 8; date += byteArray[0] & 0xFF; String query = Core.HUFFMAN.decode(byteArray, 8); StringTokenizer tokenizer = new StringTokenizer(query, " "); String command = tokenizer.nextToken(); if (command.equals("spam")) { User user = null; while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.endsWith(":") && Domain.isEmail(token.substring(0, token.length() - 1))) { user = User.get(token.substring(0, token.length() - 1)); } } if (user == null) { return "REMOVE"; } else { userList.add(user); User.Query userQuery = user.getQuery(date); if (userQuery == null) { return "REMOVE"; } else { String result = getHoldStatus(client, date, userQuery); User.storeDB(date, userQuery); return result; } } } else { return "INVALID"; } } else { return "INVALID"; } } catch (ProcessException ex) { Server.logError(ex); return "ERROR"; } } } public static TreeSet<String> addComplainURLSafe(String origin, String ticket, String result) throws ProcessException { if (ticket == null) { return null; } else { try { long time = System.currentTimeMillis(); byte[] byteArray = Server.decryptToByteArrayURLSafe(ticket); if (byteArray.length > 8) { long date = byteArray[7] & 0xFF; date <<= 8; date += byteArray[6] & 0xFF; date <<= 8; date += byteArray[5] & 0xFF; date <<= 8; date += byteArray[4] & 0xFF; date <<= 8; date += byteArray[3] & 0xFF; date <<= 8; date += byteArray[2] & 0xFF; date <<= 8; date += byteArray[1] & 0xFF; date <<= 8; date += byteArray[0] & 0xFF; if (System.currentTimeMillis() - date > 432000000) { // Ticket vencido com mais de 5 dias. throw new ProcessException("TICKET EXPIRED"); } else { String query = Core.HUFFMAN.decode(byteArray, 8); StringTokenizer tokenizer = new StringTokenizer(query, " "); String command = tokenizer.nextToken(); if (command.equals("spam")) { StringBuilder builder = new StringBuilder(); String recipient = null; User user = null; TreeSet<String> tokenSet = new TreeSet<String>(); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (isValidReputation(token)) { tokenSet.add(token); if (builder.length() > 0) { builder.append(' '); } builder.append(token); } else if (token.startsWith(">") && Domain.isEmail(token.substring(1))) { recipient = token.substring(1); } else if (token.endsWith(":") && Domain.isEmail(token.substring(0, token.length() - 1))) { user = User.get(token.substring(0, token.length() - 1)); } } TreeSet<String> blackSet = new TreeSet<String>(); for (String key : expandTokenSet(tokenSet)) { Distribution distribution = CacheDistribution.get(key, true); if (Ignore.contains(key)) { distribution.addHam(date); } else if (distribution.addSpam(date)) { distribution.getStatus(key); Peer.sendToAll(key, distribution); } blackSet.add(key); } if (user != null && result != null) { user.setResult(date, result); } Server.log(time, Core.Level.DEBUG, "CMPLN", origin, ticket, blackSet, recipient); return blackSet; } else { throw new ProcessException("TICKET INVALID"); } } } else { throw new ProcessException("TICKET INVALID"); } } catch (ProcessException ex) { return addComplain(origin, ticket); } } } public static TreeSet<String> addComplain(String origin, long date, TreeSet<String> tokenSet, String recipient) throws ProcessException { if (tokenSet == null) { return null; } else { long time = System.currentTimeMillis(); TreeSet<String> blackSet = new TreeSet<String>(); for (String key : expandTokenSet(tokenSet)) { Distribution distribution = CacheDistribution.get(key, true); if (Ignore.contains(key)) { distribution.addHam(date); } else if (distribution.addSpam(date)) { distribution.getStatus(key); Peer.sendToAll(key, distribution); } blackSet.add(key); } Server.log(time, Core.Level.DEBUG, "CMPLN", origin, tokenSet.toString(), blackSet, recipient); return blackSet; } } public static TreeSet<String> getComplain(String ticket) throws ProcessException { if (ticket == null) { return null; } else { TreeSet<String> tokenSet = new TreeSet<String>(); TreeSet<String> blackSet = new TreeSet<String>(); String registry = Server.decrypt(ticket); int index = registry.indexOf(' '); registry = registry.substring(index + 1); StringTokenizer tokenizer = new StringTokenizer(registry, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (isValidReputation(token)) { tokenSet.add(token); } } for (String key : expandTokenSet(tokenSet)) { if (!Ignore.contains(key)) { blackSet.add(key); } } return blackSet; } } /** * Remove uma nova reclamao de SPAM. * * @param ticket o ticket da mensagem original. * @throws ProcessException se houver falha no processamento do ticket. */ public static TreeSet<String> deleteComplain(String origin, String ticket) throws ProcessException { if (ticket == null) { return null; } else { long time = System.currentTimeMillis(); TreeSet<String> tokenSet = new TreeSet<String>(); String registry = Server.decrypt(ticket); int index = registry.indexOf(' '); Date date = getTicketDate(registry.substring(0, index)); registry = registry.substring(index + 1); StringTokenizer tokenizer = new StringTokenizer(registry, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (isValidReputation(token)) { tokenSet.add(token); } } for (String key : expandTokenSet(tokenSet)) { Distribution distribution = CacheDistribution.get(key, false); if (distribution != null && distribution.removeSpam(date.getTime())) { distribution.getStatus(key); Peer.sendToAll(key, distribution); } } Server.logQuery(time, "CLEAR", origin, tokenSet); return tokenSet; } } public static TreeSet<String> deleteComplainURLSafe(String origin, String ticket) throws ProcessException { if (ticket == null) { return null; } else { try { long time = System.currentTimeMillis(); byte[] byteArray = Server.decryptToByteArrayURLSafe(ticket); if (byteArray.length > 8) { long date = byteArray[7] & 0xFF; date <<= 8; date += byteArray[6] & 0xFF; date <<= 8; date += byteArray[5] & 0xFF; date <<= 8; date += byteArray[4] & 0xFF; date <<= 8; date += byteArray[3] & 0xFF; date <<= 8; date += byteArray[2] & 0xFF; date <<= 8; date += byteArray[1] & 0xFF; date <<= 8; date += byteArray[0] & 0xFF; if (System.currentTimeMillis() - date > 432000000) { // Ticket vencido com mais de 5 dias. throw new ProcessException("TICKET EXPIRED"); } else { String query = Core.HUFFMAN.decode(byteArray, 8); StringTokenizer tokenizer = new StringTokenizer(query, " "); String command = tokenizer.nextToken(); if (command.equals("spam")) { StringBuilder builder = new StringBuilder(); String recipient = null; TreeSet<String> tokenSet = new TreeSet<String>(); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (isValidReputation(token)) { tokenSet.add(token); if (builder.length() > 0) { builder.append(' '); } builder.append(token); } else if (token.startsWith(">") && Domain.isEmail(token.substring(1))) { recipient = token.substring(1); } } for (String key : expandTokenSet(tokenSet)) { Distribution distribution = CacheDistribution.get(key, false); if (distribution != null && distribution.removeSpam(date)) { distribution.getStatus(key); Peer.sendToAll(key, distribution); } } Server.logQuery(time, "CLEAR", origin, tokenSet); return tokenSet; } else { throw new ProcessException("TICKET INVALID"); } } } else { throw new ProcessException("TICKET INVALID"); } } catch (ProcessException ex) { return deleteComplain(origin, ticket); } } } /** * Classe que representa o cache de registros de distribuio de * responsveis. */ private static class CacheDistribution { /** * Mapa de distribuio binomial dos tokens encontrados. */ private static final TreeMap<String, Distribution> MAP = new TreeMap<String, Distribution>(); /** * Flag que indica se o cache foi modificado. */ private static boolean CHANGED = false; private static synchronized Distribution dropExact(String key) { Distribution ret = MAP.remove(key); if (ret != null) { CHANGED = true; } return ret; } private static synchronized Distribution putExact(String key, Distribution value) { Distribution ret = MAP.put(key, value); if (!value.equals(ret)) { CHANGED = true; } return ret; } private static synchronized TreeSet<String> keySet() { TreeSet<String> keySet = new TreeSet<String>(); keySet.addAll(MAP.keySet()); return keySet; } private static synchronized HashMap<String, Distribution> getMap() { HashMap<String, Distribution> map = new HashMap<String, Distribution>(); map.putAll(MAP); return map; } private static synchronized Distribution get(String key) { return MAP.get(key); } private static HashMap<String, Distribution> getCloneMap() { HashMap<String, Distribution> map = new HashMap<String, Distribution>(); for (String key : keySet()) { Distribution distribution = get(key); if (distribution != null) { map.put(key, distribution.replicate()); } } return map; } // private static synchronized NavigableMap<String,Distribution> getSubMap( // String fromKey, String toKey) { // return MAP.subMap(fromKey, false, toKey, false); // } private static synchronized NavigableMap<String, Distribution> getInclusiveSubMap(String fromKey, String toKey) { return MAP.subMap(fromKey, true, toKey, true); } private static Distribution getExact(String host) { return MAP.get(host); } private static boolean isChanged() { return CHANGED; } private static void setStored() { CHANGED = false; } private static void setLoaded() { CHANGED = false; } private static void store(boolean clone) { if (isChanged()) { try { // Server.logTrace("storing distribution.map"); long time = System.currentTimeMillis(); File file = new File("./data/distribution.map"); HashMap<String, Distribution> map; if (clone) { map = getCloneMap(); } else { 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); } } } private static void load() { long time = System.currentTimeMillis(); File file = new File("./data/distribution.map"); if (file.exists()) { try { Map<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 Distribution) { Distribution distribution = (Distribution) value; if (distribution.hamSet == null) { distribution.hamSet = new TreeSet<Long>(); } if (distribution.spamSet == null) { distribution.spamSet = new TreeSet<Long>(); } if (distribution.status == Status.WHITE) { distribution.status = Status.GREEN; } if (distribution.status == Status.GRAY) { distribution.status = Status.YELLOW; } if (distribution.status == Status.BLACK) { distribution.status = Status.RED; } if (distribution.frequency != null) { putExact(key.toLowerCase(), distribution); } distribution.hairCut(); } } setLoaded(); Server.logLoad(time, file); } catch (Exception ex) { Server.logError(ex); } } } private static void dropExpired() { TreeSet<String> distributionKeySet = new TreeSet<String>(); distributionKeySet.addAll(keySet()); for (String token : distributionKeySet) { Distribution distribution = getExact(token); if (distribution != null) { if (distribution.hasLastQuery() && distribution.isExpired14()) { long time = System.currentTimeMillis(); distribution = drop(token); if (distribution != null) { Server.log(time, Core.Level.DEBUG, "REPTN", token, "EXPIRED"); } } else if (distribution.dropExpiredQuery()) { distribution.getStatus(token); Peer.sendToAll(token, distribution); } else { distribution.hairCut(); } } } } private static Distribution drop(String key) { Distribution distribution = dropExact(key); if (distribution != null) { Peer.sendToAll(key, null); } return distribution; } private static TreeMap<String, Distribution> getAll(String value) { TreeMap<String, Distribution> map = new TreeMap<String, Distribution>(); NavigableMap<String, Distribution> subMap; Distribution distribution; if (Subnet.isValidIP(value)) { String ip = Subnet.normalizeIP(value); distribution = getExact(ip); if (distribution != null) { map.put(ip, distribution); } } else if (Subnet.isValidCIDR(value)) { String cidr = Subnet.normalizeCIDR(value); String ipFirst = Subnet.getFirstIP(cidr); String ipLast = Subnet.getLastIP(cidr); if (ipFirst.compareTo(ipLast) > 0) { subMap = getInclusiveSubMap(ipLast, ipFirst); } else { subMap = getInclusiveSubMap(ipFirst, ipLast); } for (String ip : subMap.keySet()) { if (Subnet.containsIP(cidr, ip)) { distribution = getExact(ip); if (distribution != null) { map.put(ip, distribution); } } } } else if (Domain.isHostname(value)) { String host = Domain.normalizeHostname(value, true); do { int index = host.indexOf('.') + 1; host = host.substring(index); if ((distribution = getExact('.' + host)) != null) { map.put('.' + host, distribution); } else if ((distribution = getExact('@' + host)) != null) { map.put('@' + host, distribution); } } while (host.contains(".")); } else { distribution = getExact(value); if (distribution != null) { map.put(value, distribution); } } return map; } /** * Retorna uma distribuio binomial do whois informado. * * @param key o whois cuja distribuio deve ser retornada. * @return uma distribuio binomial do whois informado. */ private static Distribution get(String key, boolean create) { Distribution distribution = getExact(key); if (distribution != null) { if (distribution.isExpired7()) { distribution.reset(); } } else if (create) { distribution = new Distribution(); putExact(key, distribution); } else { distribution = null; } return distribution; } private static TreeMap<String, Distribution> getTreeMap() { TreeSet<String> keySet = keySet(); keySet.addAll(Peer.getReputationKeyAllSet()); TreeMap<String, Distribution> distributionMap = new TreeMap<String, Distribution>(); for (String key : keySet) { Distribution distribution = get(key, true); distributionMap.put(key, distribution); } return distributionMap; } private static TreeMap<String, Distribution> getTreeMapIPv4() { TreeMap<String, Distribution> distributionMap = new TreeMap<String, Distribution>(); for (String key : keySet()) { if (SubnetIPv4.isValidIPv4(key)) { Distribution distribution = getExact(key); if (distribution != null) { distributionMap.put(key, distribution); } } } return distributionMap; } private static TreeMap<String, Distribution> getTreeMapIPv6() { TreeMap<String, Distribution> distributionMap = new TreeMap<String, Distribution>(); for (String key : keySet()) { if (SubnetIPv6.isValidIPv6(key)) { Distribution distribution = getExact(key); if (distribution != null) { distributionMap.put(key, distribution); } } } return distributionMap; } private static TreeMap<String, Binomial> getTreeMapExtendedCIDR() { TreeMap<String, Binomial> binomialMap = new TreeMap<String, Binomial>(); for (String cidr : Block.getExtendedCIDR()) { Binomial binomial = new Binomial(Status.BLOCK); binomialMap.put(cidr, binomial); } for (String key : keySet()) { if (SubnetIPv4.isValidIPv4(key)) { String expandedIP = null; Distribution distribution = getExact(key); if (distribution != null) { expandedIP = SubnetIPv4.expandIPv4(key); String floor = binomialMap.floorKey(expandedIP + "/9"); if (floor != null && floor.contains(".")) { String cidr = SubnetIPv4.normalizeCIDRv4(floor); if (SubnetIPv4.containsIPv4(cidr, key)) { Binomial binomial = binomialMap.get(floor); binomial.add(key, distribution); distribution = null; } } } if (distribution != null && expandedIP != null) { Binomial binomial = new Binomial(key, distribution); binomialMap.put(expandedIP + "/32", binomial); } } } return binomialMap; } private static TreeMap<String, Distribution> getMap(TreeSet<String> tokenSet) { TreeMap<String, Distribution> distributionMap = new TreeMap<String, Distribution>(); for (String token : tokenSet) { Distribution distribution = getExact(token); if (distribution != null) { distributionMap.put(token, distribution); } } return distributionMap; } } public static void dropExpiredDistribution() { CacheDistribution.dropExpired(); } public static TreeMap<String, Distribution> getDistributionMap() { return CacheDistribution.getTreeMap(); } public static Distribution getDistribution(String token) { return CacheDistribution.get(token, false); } public static Distribution getDistribution(String token, boolean create) { return CacheDistribution.get(token, create); } public static TreeMap<String, Distribution> getDistributionMapIPv4() { return CacheDistribution.getTreeMapIPv4(); } public static TreeMap<String, Distribution> getDistributionMapIPv6() { return CacheDistribution.getTreeMapIPv6(); } public static TreeMap<String, Binomial> getDistributionMapExtendedCIDR() { return CacheDistribution.getTreeMapExtendedCIDR(); } public static void dropDistribution(String token) { CacheDistribution.drop(token); } private static boolean matches(String regex, String token) { try { return Pattern.matches(regex, token); } catch (Exception ex) { return false; } } private static boolean isWHOIS(String token) { return matches("^WHOIS(/[a-z-]+)+((=[a-zA-Z0-9@/.-]+)|((<|>)[0-9]+))$", token); } public static boolean isREGEX(String token) { return matches("^REGEX=[^ ]+$", token); } private static boolean isDNSBL(String token) { if (token.startsWith("DNSBL=") && token.contains(";")) { int index1 = token.indexOf('='); int index2 = token.indexOf(';'); String server = token.substring(index1 + 1, index2); String value = token.substring(index2 + 1); return Domain.isHostname(server) && Subnet.isValidIP(value); } else { return false; } } private static boolean isCIDR(String token) { return matches("^CIDR=(" + "((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]){1,3}\\.){1,3}" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/[0-9]{1,2})" + "|" + "((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + "([0-9a-fA-F]{1,4}:){1,7}:|" + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,})" + "/[0-9]{1,3})" + ")$", token); } private static String normalizeCIDR(String token) { if (token == null) { return null; } else if (token.startsWith("CIDR=")) { int index = token.indexOf('='); String cidr = token.substring(index + 1); return "CIDR=" + Subnet.normalizeCIDR(cidr); } else { return null; } } public static String normalizeTokenFull(String token) throws ProcessException { return normalizeToken(token, true, true, true, true, true, true); } public static String normalizeToken(String token, boolean canWHOIS, boolean canREGEX, boolean canCIDR, boolean canDNSBL, boolean canHREF, boolean canNOTPASS) throws ProcessException { if (token == null || token.length() == 0) { return null; } else if (canWHOIS && isWHOIS(token)) { return token; } else if (canREGEX && isREGEX(token)) { try { int index = token.indexOf('='); String regex = token.substring(index + 1); Pattern.compile(regex); return token; } catch (Exception ex) { return null; } } else if (canCIDR && isCIDR(token)) { return normalizeCIDR(token); } else if (canCIDR && SubnetIPv4.isValidIPv4(token)) { return "CIDR=" + SubnetIPv4.normalizeIPv4(token) + "/32"; } else if (canCIDR && SubnetIPv6.isValidIPv6(token)) { return "CIDR=" + SubnetIPv6.normalizeIPv6(token) + "/128"; } else if (canWHOIS && Owner.isOwnerID(token)) { return "WHOIS/ownerid=" + Owner.normalizeID(token); } else if (canCIDR && Subnet.isValidCIDR(token)) { return "CIDR=" + Subnet.normalizeCIDR(token); } else if (canDNSBL && isDNSBL(token)) { int index1 = token.indexOf('='); int index2 = token.indexOf(';'); String server = token.substring(index1 + 1, index2); String value = token.substring(index2 + 1); server = Domain.normalizeHostname(server, false); value = Subnet.normalizeIP(value); return "DNSBL=" + server + ';' + value; } else if (canHREF && token.startsWith("HREF=")) { int index = token.indexOf('='); String value = token.substring(index + 1); if (Owner.isOwnerID(value)) { return "HREF=" + Owner.normalizeID(value); } else { return "HREF=" + normalizeToken(value, false, false, false, false, false, false); } } else { token = Core.removerAcentuacao(token); String recipient = ""; if (token.contains(">")) { int index = token.indexOf('>'); recipient = token.substring(index + 1); token = token.substring(0, index); if (Domain.isEmail(recipient)) { recipient = '>' + recipient.toLowerCase(); } else if (recipient.startsWith("@") && Domain.containsDomain(recipient.substring(1))) { recipient = '>' + recipient.toLowerCase(); } else { return null; } } String qualif = ""; if (token.contains(";")) { int index = token.indexOf(';'); qualif = token.substring(index); if (qualif.equals(";PASS")) { token = token.substring(0, index); } else if (qualif.equals(";SOFTFAIL")) { token = token.substring(0, index); } else if (qualif.equals(";NEUTRAL")) { token = token.substring(0, index); } else if (qualif.equals(";NONE")) { token = token.substring(0, index); } else if (qualif.equals(";FAIL")) { return null; } else if (canNOTPASS && qualif.equals(";NOTPASS")) { token = token.substring(0, index); } else if (Domain.isHostname(qualif.substring(1))) { qualif = ";" + Domain.normalizeHostname(qualif.substring(1), false); token = token.substring(0, index); } else if (Subnet.isValidIP(qualif.substring(1))) { qualif = ";" + Subnet.normalizeIP(qualif.substring(1)); token = token.substring(0, index); } else { // Sintaxe com erro. return null; } } if (Domain.isEmail(token)) { return token.toLowerCase() + qualif + recipient; } else if (token.endsWith("@")) { return token.toLowerCase() + qualif + recipient; } else if (token.startsWith("@") && Domain.containsDomain(token.substring(1))) { return token.toLowerCase() + qualif + recipient; } else if (!token.contains("@") && Domain.containsDomain(token)) { return Domain.extractHost(token, true) + qualif + recipient; } else if (token.startsWith(".") && Domain.containsDomain(token.substring(1))) { return Domain.extractHost(token, true) + qualif + recipient; } else if (Subnet.isValidIP(token)) { return Subnet.normalizeIP(token) + qualif + recipient; } else { return null; } } } public static TreeSet<String> clear(String token) { TreeSet<String> clearSet = new TreeSet<String>(); TreeMap<String, Distribution> distribuitonMap = CacheDistribution.getAll(token); for (String key : distribuitonMap.keySet()) { Distribution distribution = distribuitonMap.get(key); if (distribution != null) { if (distribution.clear()) { clearSet.add(key); distribution.getStatus(token); Peer.sendToAll(key, distribution); } } if (Block.dropExact(key)) { clearSet.add(key); } } for (String key : Block.getAllTokens(token)) { if (Block.dropExact(key)) { clearSet.add(key); } } // for (String key : Peer.clearAllReputation(token)) { // clearSet.add(key); // } return clearSet; } public static TreeSet<String> getGuessSet() throws ProcessException { return CacheGuess.get(); } public static HashMap<String, String> getGuessMap() throws ProcessException { return CacheGuess.getMap(); } /** * Classe que representa o cache de "best-guess" exclusivos. */ private static class CacheGuess { /** * http://www.openspf.org/FAQ/Best_guess_record porm compatvel com * IPv6 */ private static final String BEST_GUESS = "v=spf1 a/24//48 mx/24//48 ptr ?all"; /** * Mapa de registros manuais de SPF caso o domnio no tenha um. */ private static final HashMap<String, String> MAP = new HashMap<String, String>(); /** * Flag que indica se o cache foi modificado. */ private static boolean CHANGED = false; private static synchronized String dropExact(String token) { String ret = MAP.remove(token); if (ret == null) { return null; } else { CHANGED = true; return ret; } } private static synchronized String putExact(String key, String value) { String ret = MAP.put(key, value); if (!value.equals(ret)) { CHANGED = true; } return ret; } private static TreeSet<String> keySet() { TreeSet<String> keySet = new TreeSet<String>(); keySet.addAll(MAP.keySet()); return keySet; } private static HashMap<String, String> getMap() { HashMap<String, String> map = new HashMap<String, String>(); map.putAll(MAP); return map; } private static boolean containsExact(String address) { return MAP.containsKey(address); } private static String getExact(String host) { return MAP.get(host); } private static boolean isChanged() { return CHANGED; } private static void setStored() { CHANGED = false; } private static void setLoaded() { CHANGED = false; } private static boolean add(String hostname, String spf) throws ProcessException { hostname = Domain.extractHost(hostname, false); if (!Domain.containsDomain(hostname)) { throw new ProcessException("ERROR: HOSTNAME INVALID"); } else if (!spf.equals(putExact("." + hostname, spf))) { CacheSPF.refresh(hostname, true); return true; } else { return false; } } private static TreeSet<String> dropAll() throws ProcessException { TreeSet<String> guessSet = new TreeSet<String>(); for (String domain : keySet()) { String spf = dropExact(domain); if (spf != null) { guessSet.add(domain + " \"" + spf + "\""); } } return guessSet; } private static boolean drop(String hostname) throws ProcessException { hostname = Domain.extractHost(hostname, false); if (!Domain.containsDomain(hostname)) { throw new ProcessException("ERROR: HOSTNAME INVALID"); } else if (dropExact("." + hostname) == null) { return false; } else { CacheSPF.refresh(hostname, true); return true; } } private static boolean contains(String host) { if (!host.startsWith(".")) { host = "." + host; } return containsExact(host); } private static TreeSet<String> get() throws ProcessException { TreeSet<String> guessSet = new TreeSet<String>(); for (String domain : keySet()) { String spf = get(domain); guessSet.add(domain + " \"" + spf + "\""); } return guessSet; } private static String get(String host) { if (!host.startsWith(".")) { host = "." + host; } String guess = getExact(host); if (guess == null) { // Se no hoouver palpite SPF especfico para o hostname, // utilizar o palpite padro, porm adaptado para IPv6. // http://www.openspf.org/FAQ/Best_guess_record return BEST_GUESS; } else { // Significa que um palpite SPF especfico // foi registrado para este hostname. // Neste caso utilizar o paltpite especfico. return guess; } } private static void store() { if (isChanged()) { try { // Server.logTrace("storing guess.map"); long time = System.currentTimeMillis(); File file = new File("./data/guess.map"); HashMap<String, String> 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); } } } private static void load() { long time = System.currentTimeMillis(); File file = new File("./data/guess.map"); if (file.exists()) { try { HashMap<String, String> map; FileInputStream fileInputStream = new FileInputStream(file); try { map = SerializationUtils.deserialize(fileInputStream); } finally { fileInputStream.close(); } for (String key : map.keySet()) { String value = map.get(key); putExact(key, value); } setLoaded(); Server.logLoad(time, file); } catch (Exception ex) { Server.logError(ex); } } } } public static boolean addGuess(String host, String spf) throws ProcessException { return CacheGuess.add(host, spf); } public static boolean dropGuess(String host) throws ProcessException { return CacheGuess.drop(host); } public static TreeSet<String> dropAllGuess() throws ProcessException { return CacheGuess.dropAll(); } public static void storeGuess() { CacheGuess.store(); } public static void storeSPF() { CacheSPF.store(); } /** * Armazenamento de cache em disco. */ public static void store(boolean clone) { CacheSPF.store(); CacheDistribution.store(clone); CacheGuess.store(); CacheHELO.store(); } /** * Carregamento de cache do disco. */ public static void load() { CacheSPF.load(); CacheDistribution.load(); CacheGuess.load(); CacheHELO.load(); } public static String getUniqueIPv4(String helo) { return CacheHELO.getUniqueIPv4(helo); } public static String getUniqueIPv6(String helo) { return CacheHELO.getUniqueIPv6(helo); } /** * Classe que representa o cache de resoluo de HELO. */ private static class CacheHELO { /** * Mapa de atributos da verificao de HELO. */ private static final HashMap<String, HELO> MAP = new HashMap<String, HELO>(); /** * O prximo registro HELO que deve ser atualizado. */ private static String hostRefresh = null; private static HELO heloRefresh = null; /** * Flag que indica se o cache foi modificado. */ private static boolean CHANGED = false; private static synchronized HELO dropExact(String token) { HELO ret = MAP.remove(token); if (ret != null) { CHANGED = true; } return ret; } private static synchronized HELO putExact(String key, HELO value) { HELO ret = MAP.put(key, value); if (!value.equals(ret)) { CHANGED = true; } return ret; } private static synchronized TreeSet<String> keySet() { TreeSet<String> keySet = new TreeSet<String>(); keySet.addAll(MAP.keySet()); return keySet; } private static synchronized HashMap<String, HELO> getMap() { HashMap<String, HELO> map = new HashMap<String, HELO>(); map.putAll(MAP); return map; } private static HELO getExact(String host) { return MAP.get(host); } private static synchronized String getRefreshHELO() { String helo = hostRefresh; hostRefresh = null; heloRefresh = null; return helo; } private static synchronized void addQuery(String host, HELO helo) { helo.queryCount++; helo.lastQuery = System.currentTimeMillis(); if (hostRefresh == null || heloRefresh == null) { hostRefresh = host; heloRefresh = helo; } else if (heloRefresh.queryCount < helo.queryCount) { hostRefresh = host; heloRefresh = helo; } } /* * Classe para guardar os atributos da consulta. */ private static final class HELO implements Serializable { private static final long serialVersionUID = 1L; private TreeSet<String> addressSet = null; private String address4 = null; private String address6 = null; private int queryCount = 0; private long lastQuery; private HELO(String hostname) { this.lastQuery = System.currentTimeMillis(); refresh(hostname); } public synchronized void refresh(String hostname) { long time = System.currentTimeMillis(); try { TreeSet<String> ipv4Set = 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); ipv4Set.add(address); } } } } TreeSet<String> ipv6Set = new TreeSet<String>(); 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); ipv6Set.add(address); } } } } this.addressSet = new TreeSet<String>(); this.addressSet.addAll(ipv4Set); this.addressSet.addAll(ipv6Set); if (ipv4Set.size() == 1) { this.address4 = ipv4Set.first(); } else { this.address4 = null; } if (ipv6Set.size() == 1) { this.address6 = ipv6Set.first(); } else { this.address6 = null; } Server.logLookupHELO(time, hostname, addressSet.toString()); } catch (CommunicationException ex) { Server.logLookupHELO(time, hostname, "TIMEOUT"); } catch (ServiceUnavailableException ex) { Server.logLookupHELO(time, hostname, "SERVFAIL"); } catch (NameNotFoundException ex) { this.addressSet = null; this.address4 = null; this.address6 = null; Server.logLookupHELO(time, hostname, "NXDOMAIN"); } catch (NamingException ex) { this.addressSet = null; this.address4 = null; this.address6 = null; Server.logLookupHELO(time, hostname, "ERROR " + ex.getClass() + " " + ex.getExplanation()); } finally { this.queryCount = 0; CHANGED = true; } } public TreeSet<String> getAddressSet() { TreeSet<String> set = new TreeSet<String>(); set.addAll(this.addressSet); return set; } public boolean contains(String ip) { if ((ip = Subnet.normalizeIP(ip)) == null) { return false; } else if (addressSet == null) { return false; } else { return addressSet.contains(ip); } } public String getAddress4() { return address4; } public String getAddress6() { return address6; } public boolean isExpired7() { return System.currentTimeMillis() - lastQuery > 604800000; } public boolean isExpired14() { return System.currentTimeMillis() - lastQuery > 1209600000; } } public static String getUniqueIPv4(String helo) { if ((helo = Domain.extractHost(helo, false)) == null) { return null; } else { HELO heloObj = getExact(helo); if (heloObj == null) { return null; } else { return heloObj.getAddress4(); } } } public static String getUniqueIPv6(String helo) { if ((helo = Domain.extractHost(helo, false)) == null) { return null; } else { HELO heloObj = getExact(helo); if (heloObj == null) { return null; } else { return heloObj.getAddress6(); } } } public static boolean match(String ip, String helo, boolean refresh) { if (ip == null) { return false; } else if ((helo = Domain.extractHost(helo, false)) == null) { return false; } else { HELO heloObj = getExact(helo); if (heloObj == null) { heloObj = new HELO(helo); putExact(helo, heloObj); } else if (refresh) { heloObj.refresh(helo); addQuery(helo, heloObj); CHANGED = true; } else { addQuery(helo, heloObj); CHANGED = true; } return heloObj.contains(ip); } } private static void dropExpired() { for (String helo : keySet()) { long time = System.currentTimeMillis(); HELO heloObj = getExact(helo); if (heloObj != null && heloObj.isExpired14()) { heloObj = dropExact(helo); if (heloObj != null) { Server.logLookupHELO(time, helo, "EXPIRED"); } } } } /** * Atualiza o registro mais consultado. */ private static void refresh() { String heloMax = getRefreshHELO(); HELO heloObjMax = getExact(heloMax); if (heloObjMax == null) { for (String hostname : keySet()) { HELO heloObj = getExact(hostname); if (heloObj != null) { if (heloObjMax == null) { heloMax = hostname; heloObjMax = heloObj; } else if (heloObjMax.queryCount < heloObj.queryCount) { heloMax = hostname; heloObjMax = heloObj; } } } } if (heloMax != null && heloObjMax != null && heloObjMax.queryCount > 3) { heloObjMax.refresh(heloMax); } } private static void store() { if (CHANGED) { try { // Server.logTrace("storing helo.map"); long time = System.currentTimeMillis(); File file = new File("./data/helo.map"); HashMap<String, HELO> 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); } } } private static void load() { long time = System.currentTimeMillis(); File file = new File("./data/helo.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 HELO) { HELO helo = (HELO) value; // helo.update(); putExact(key, helo); } } CHANGED = false; Server.logLoad(time, file); } catch (Exception ex) { Server.logError(ex); } } } } public static void dropExpiredHELO() { CacheHELO.dropExpired(); } public static boolean matchHELO(String ip, String helo, boolean refresh) { return CacheHELO.match(ip, helo, refresh); } public static boolean matchHELO(String ip, String helo) { return CacheHELO.match(ip, helo, false); } protected static String processPostfixSPF(InetAddress ipAddress, Client client, User user, String ip, String sender, String helo, String recipient, LinkedList<User> userResult) throws ProcessException { if (sender == null) { sender = null; } else if (sender.trim().length() == 0) { sender = null; } else if (Domain.isEmail(sender)) { sender = sender.toLowerCase(); } else { return "action=554 5.7.1 SPFBL " + sender + " " + "is not a valid e-mail address.\n\n"; } if (recipient == null) { recipient = null; } else if (recipient.trim().length() == 0) { recipient = null; } else if (Domain.isEmail(recipient)) { recipient = recipient.toLowerCase(); } else { return "action=554 5.7.1 SPFBL " + recipient + " " + "is not a valid e-mail address.\n\n"; } if (!Domain.isHostname(helo)) { helo = null; } if (!Subnet.isValidIP(ip)) { return "action=554 5.7.1 SPFBL " + ip + " is not a valid public IP.\n\n"; } else if (Subnet.isReservedIP(ip)) { // Message from LAN. return "action=DUNNO\n\n"; } else if (client != null && client.containsFull(ip)) { // Message from LAN. return "action=DUNNO\n\n"; } else { try { TreeSet<String> tokenSet = new TreeSet<String>(); ip = Subnet.normalizeIP(ip); tokenSet.add(Subnet.normalizeIP(ip)); if (Domain.isValidEmail(recipient)) { // Se houver um remetente vlido, // Adicionar no ticket para controle externo. tokenSet.add('>' + recipient); } if (recipient != null) { User recipientUser = User.get(recipient); if (recipientUser == null) { // Se a consulta originar de destinatrio com postmaster cadastrado, // considerar o prprio postmaster como usurio da consulta. int index = recipient.indexOf('@'); String postmaster = "postmaster" + recipient.substring(index); User postmasterUser = User.get(postmaster); if (postmasterUser != null) { user = postmasterUser; } } else { user = recipientUser; } } if (user != null) { userResult.add(user); tokenSet.add(user.getEmail() + ':'); } else if (client != null && client.hasEmail()) { tokenSet.add(client.getEmail() + ':'); } // Passar a acompanhar todos os // HELO quando apontados para o IP para // uma nova forma de interpretar dados. String hostname; if (CacheHELO.match(ip, helo, false)) { hostname = Domain.normalizeHostname(helo, true); } else { hostname = Reverse.getHostname(ip); hostname = Domain.normalizeHostname(hostname, true); } if (hostname == null) { Server.logDebug("no rDNS for " + ip + "."); } else if (Domain.isOfficialTLD(hostname)) { return "action=554 5.7.1 SPFBL " + hostname + " is a reserved domain.\n\n"; } else { // Verificao de pilha dupla, // para pontuao em ambas pilhas. String ipv4 = CacheHELO.getUniqueIPv4(hostname); String ipv6 = CacheHELO.getUniqueIPv6(hostname); if (ip.equals(ipv6) && CacheHELO.match(ipv4, hostname, false)) { // Equivalncia de pilha dupla se // IPv4 for nico para o hostname. tokenSet.add(ipv4); } else if (ip.equals(ipv4) && CacheHELO.match(ipv6, hostname, false)) { // Equivalncia de pilha dupla se // IPv6 for nico para o hostname. tokenSet.add(ipv6); } } if (Generic.containsGenericSoft(hostname)) { // Quando o reverso for // genrico, no consider-lo. hostname = null; } else if (hostname != null) { tokenSet.add(hostname); } String result; LinkedList<String> logList = new LinkedList<String>(); SPF spf; if (sender == null) { spf = null; result = "NONE"; } else if (Domain.isOfficialTLD(sender)) { spf = null; result = "NONE"; } else if (Generic.containsGeneric(sender)) { spf = null; result = "NONE"; } else if ((spf = CacheSPF.get(sender)) == null) { result = "NONE"; } else if (spf.isInexistent()) { result = "NONE"; } else { result = spf.getResult(ip, sender, helo, logList); } String origem; String fluxo; String mx = Domain.extractHost(sender, true); if (user != null && user.isLocal()) { // Message from local user. return "action=DUNNO\n\n"; } else if (recipient != null && result.equals("PASS")) { if (recipient.endsWith(mx)) { // Message from same domain. return "action=DUNNO\n\n"; } else if (recipient.equals(Core.getAbuseEmail()) && User.exists(sender, "postmaster" + mx)) { // Message to abuse. return "action=DUNNO\n\n"; } } if (result.equals("PASS") || (sender != null && Provider.containsHELO(ip, helo))) { // Quando fo PASS, significa que o domnio // autorizou envio pelo IP, portanto o dono dele // responsavel pelas mensagens. if (!Provider.containsExact(mx)) { // No um provedor ento // o MX deve ser listado. tokenSet.add(mx); origem = mx; } else if (Domain.isValidEmail(sender)) { // Listar apenas o remetente se o // hostname for um provedor de e-mail. String userEmail = null; String recipientEmail = null; for (String token : tokenSet) { if (token.endsWith(":")) { userEmail = token; } else if (token.startsWith(">")) { recipientEmail = token; } } tokenSet.clear(); tokenSet.add(sender); if (userEmail != null) { tokenSet.add(userEmail); } if (recipientEmail != null) { tokenSet.add(recipientEmail); } origem = sender; } else { origem = sender; } fluxo = origem + ">" + recipient; } else if (hostname == null) { origem = (sender == null ? "" : sender + '>') + ip; fluxo = origem + ">" + recipient; } else { String dominio = Domain.extractDomain(hostname, true); origem = (sender == null ? "" : sender + '>') + (dominio == null ? hostname : dominio.substring(1)); fluxo = origem + ">" + recipient; } Long recipientTrapTime = Trap.getTimeRecipient(client, user, recipient); if (recipientTrapTime == null && White.contains(client, user, ip, sender, hostname, result, recipient)) { if (White.contains(client, user, ip, sender, hostname, result, null)) { // Limpa da lista BLOCK um possvel falso positivo. Block.clear(client, user, ip, sender, hostname, result, null); } // Calcula frequencia de consultas. String url = Core.getURL(); String ticket = SPF.addQueryHam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "WHITE"); return "action=PREPEND " + "Received-SPFBL: " + result + " " + (url == null ? ticket : url + ticket) + "\n\n"; } else if (Block.contains(client, user, ip, sender, hostname, result, recipient, true, true, true, true)) { Action action = client == null ? Action.REJECT : client.getActionBLOCK(); if (action == Action.REJECT) { // Calcula frequencia de consultas. User.Query queryLocal = SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "BLOCK"); action = client == null ? Action.FLAG : client.getActionRED(); if (action != Action.REJECT && queryLocal != null && queryLocal.needHeader()) { if (action == Action.FLAG) { queryLocal.setResult("FLAG"); return "action=PREPEND X-Spam-Flag: YES\n\n"; } else if (action == Action.HOLD) { queryLocal.setResult("HOLD"); return "action=HOLD blocked.\n\n"; } else { return "action=WARN undefined action.\n\n"; } } else { String url = Core.getUnblockURL(client, user, ip, sender, hostname, recipient); if (url == null) { return "action=554 5.7.1 SPFBL " + "you are permanently blocked in this server.\n\n"; } else { return "action=554 5.7.1 SPFBL " + "BLOCKED " + url + "\n\n"; } } } else if (action == Action.FLAG) { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FLAG"); return "action=PREPEND X-Spam-Flag: YES\n\n"; } else if (action == Action.HOLD) { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "HOLD"); return "action=HOLD blocked.\n\n"; } else { return "action=WARN undefined action.\n\n"; } } else if (Generic.containsDynamicDomain(hostname)) { // Bloquear automaticamente range de IP dinmico. String cidr = Subnet.normalizeCIDR(SubnetIPv4.isValidIPv4(ip) ? ip + "/24" : ip + "/48"); if (Block.tryOverlap(cidr)) { Server.logDebug("new BLOCK '" + cidr + "' added by '" + hostname + ";DYNAMIC'."); } else if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + hostname + ";DYNAMIC'."); } SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "action=554 5.7.1 SPFBL dynamic IP.\n\n"; } else if (spf != null && spf.isDefinitelyInexistent()) { // Bloquear automaticamente IP com reputao vermelha. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + mx + ";NXDOMAIN'."); } } Analise.processToday(ip); // O domnio foi dado como inexistente inmeras vezes. // Rejeitar e denunciar o host pois h abuso de tentativas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "NXDOMAIN"); return "action=554 5.7.1 SPFBL " + "sender has non-existent internet domain.\n\n"; } else if (spf != null && spf.isInexistent()) { Analise.processToday(ip); SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "NXDOMAIN"); return "action=554 5.7.1 SPFBL " + "sender has non-existent internet domain.\n\n"; } else if (result.equals("FAIL")) { // Bloquear automaticamente IP com reputao vermelha. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + sender + ";FAIL'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FAIL"); // Retornar REJECT somente se no houver // liberao literal do remetente com FAIL. return "action=554 5.7.1 SPFBL " + sender + " is not allowed to " + "send mail from " + ip + ".\n\n"; } else if (sender != null && !Domain.isEmail(sender)) { // Bloquear automaticamente IP com reputao vermelha. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + sender + ";INVALID'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "action=554 5.7.1 SPFBL " + sender + " is not a valid e-mail address.\n\n"; } else if (sender != null && Domain.isOfficialTLD(sender)) { // Bloquear automaticamente IP com reputao vermelha. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + sender + ";RESERVED'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "RESERVED"); return "action=554 5.7.1 SPFBL " + sender + " has a reserved domain.\n\n"; } else if (sender == null && !CacheHELO.match(ip, hostname, false)) { // Bloquear automaticamente IP com reputao ruim. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + hostname + ";INVALID'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "action=554 5.7.1 SPFBL invalid hostname.\n\n"; } else if (hostname == null && Core.isReverseRequired()) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by 'NONE'."); } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "action=554 5.7.1 SPFBL " + ip + " has no rDNS.\n\n"; } else if (recipient != null && !Domain.isValidEmail(recipient)) { Analise.processToday(ip); Analise.processToday(mx); SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INEXISTENT"); return "action=550 5.1.1 SPFBL the email account that you tried to reach does not exist.\n\n"; } else if (recipientTrapTime != null) { if (System.currentTimeMillis() > recipientTrapTime) { // Spamtrap. for (String token : tokenSet) { String block; Status status = SPF.getStatus(token); if (status == Status.RED && (block = Block.add(token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + recipient + ";SPAMTRAP'."); Peer.sendBlockToAll(block); } if (status != Status.GREEN && !Subnet.isValidIP(token) && (block = Block.addIfNotNull(user, token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + recipient + ";SPAMTRAP'."); } } Analise.processToday(ip); Analise.processToday(mx); // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "TRAP"); return "action=DISCARD SPFBL discarded by spamtrap.\n\n"; } else { // Inexistent. for (String token : tokenSet) { String block; Status status = SPF.getStatus(token); if (status == Status.RED && (block = Block.add(token)) != null) { Server.logDebug( "new BLOCK '" + block + "' added by '" + recipient + ";INEXISTENT'."); Peer.sendBlockToAll(block); } if (status != Status.GREEN && !Subnet.isValidIP(token) && (block = Block.addIfNotNull(user, token)) != null) { Server.logDebug( "new BLOCK '" + block + "' added by '" + recipient + ";INEXISTENT'."); } } Analise.processToday(ip); Analise.processToday(mx); SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INEXISTENT"); return "action=550 5.1.1 SPFBL the email account that you tried to reach does not exist.\n\n"; } } else if (Defer.count(fluxo) > Core.getFloodMaxRetry()) { Analise.processToday(ip); Analise.processToday(mx); // A origem atingiu o limite de atraso // para liberao do destinatrio. long time = System.currentTimeMillis(); Defer.end(fluxo); Server.logDefer(time, fluxo, "DEFER FLOOD"); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FLOOD"); return "action=554 5.7.1 SPFBL too many retries.\n\n"; } else if (!result.equals("PASS") && !CacheHELO.match(ip, hostname, false)) { // Bloquear automaticamente IP com reputao amarela. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + recipient + ";INVALID'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "action=554 5.7.1 SPFBL invalid hostname.\n\n"; } else if (recipient != null && recipient.startsWith("postmaster@")) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "ACCEPT"); return "action=PREPEND " + "Received-SPFBL: " + result + " " + (url == null ? ticket : url + URLEncoder.encode(ticket, "UTF-8")) + "\n\n"; } else if (result.equals("PASS") && SPF.isGood(Provider.containsExact(mx) ? sender : mx)) { // O remetente vlido e tem excelente reputao, // ainda que o provedor dele esteja com reputao ruim. String url = Core.getURL(); String ticket = SPF.addQueryHam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "ACCEPT"); return "action=PREPEND " + "Received-SPFBL: PASS " + (url == null ? ticket : url + URLEncoder.encode(ticket, "UTF-8")) + "\n\n"; } else if (SPF.hasRed(tokenSet) || Analise.isCusterRED(ip, sender, hostname)) { Analise.processToday(ip); Analise.processToday(mx); Action action = client == null ? Action.REJECT : client.getActionRED(); if (action == Action.REJECT) { // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "action=554 5.7.1 SPFBL " + "you are temporarily listed.\n\n"; } else if (action == Action.DEFER) { if (Defer.defer(fluxo, Core.getDeferTimeRED())) { // Pelo menos um identificador est listado e com atrazo programado de um dia. String url = Core.getReleaseURL(fluxo); SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "LISTED"); if (url == null || Defer.count(fluxo) > 1) { return "action=451 4.7.2 SPFBL " + "you are temporarily listed.\n\n"; } else if (result.equals("PASS") && enviarLiberacao(url, sender, recipient)) { // Envio da liberao por e-mail se // houver validao do remetente por PASS. return "action=451 4.7.2 SPFBL " + "you are temporarily listed.\n\n"; } else { return "action=451 4.7.2 SPFBL LISTED " + url + "\n\n"; } } else { // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "action=554 5.7.1 SPFBL too many retries.\n\n"; } } else if (action == Action.FLAG) { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FLAG"); return "action=PREPEND X-Spam-Flag: YES\n\n"; } else if (action == Action.HOLD) { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "HOLD"); return "action=HOLD very bad reputation.\n\n"; } else { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "UNDEFINED"); return "action=WARN undefined action.\n\n"; } } else if (Domain.isGraceTime(sender) || Domain.isGraceTime(hostname)) { Server.logTrace("domain in grace time."); for (String token : tokenSet) { String block; Status status = SPF.getStatus(token); if (status == Status.RED && (block = Block.add(token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + status + "'."); Peer.sendBlockToAll(block); } if (status != Status.GREEN && !Subnet.isValidIP(token) && (block = Block.addIfNotNull(user, token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + status + "'."); } } Analise.processToday(ip); Analise.processToday(mx); Action action = client == null ? Action.REJECT : client.getActionGRACE(); if (action == Action.REJECT) { // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "action=554 5.7.1 SPFBL " + "your domain is in grace time.\n\n"; } else if (action == Action.DEFER) { if (Defer.defer(fluxo, Core.getDeferTimeRED())) { // Pelo menos um identificador est listado e com atrazo programado de um dia. String url = Core.getReleaseURL(fluxo); SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "LISTED"); if (url == null || Defer.count(fluxo) > 1) { return "action=451 4.7.2 SPFBL " + "you are temporarily listed.\n\n"; } else if (result.equals("PASS") && enviarLiberacao(url, sender, recipient)) { // Envio da liberao por e-mail se // houver validao do remetente por PASS. return "action=451 4.7.2 SPFBL " + "you are temporarily listed.\n\n"; } else { return "action=451 4.7.2 SPFBL LISTED " + url + "\n\n"; } } else { // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "action=554 5.7.1 SPFBL too many retries.\n\n"; } } else if (action == Action.FLAG) { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FLAG"); return "action=PREPEND X-Spam-Flag: YES\n\n"; } else if (action == Action.HOLD) { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "HOLD"); return "action=HOLD domain in grace time.\n\n"; } else { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "UNDEFINED"); return "action=WARN undefined action.\n\n"; } } else if (SPF.hasYellow(tokenSet) && Defer.defer(fluxo, Core.getDeferTimeYELLOW())) { Analise.processToday(ip); Analise.processToday(mx); Action action = client == null ? Action.DEFER : client.getActionYELLOW(); if (action == Action.DEFER) { // Pelo menos um identificador est em greylisting com atrazo programado de 10min. SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "GREYLISTED"); return "action=451 4.7.1 SPFBL you are greylisted.\n\n"; } else if (action == Action.HOLD) { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "HOLD"); return "action=HOLD not good reputation.\n\n"; } else { SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "UNDEFINED"); return "action=WARN undefined action.\n\n"; } } else if (SPF.isFlood(tokenSet) && !Provider.containsHELO(ip, hostname) && Defer.defer(origem, Core.getDeferTimeFLOOD())) { Analise.processToday(ip); Analise.processToday(mx); // Pelo menos um identificador est com frequncia superior ao permitido. Server.logDebug("FLOOD " + tokenSet); SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "GREYLISTED"); return "action=451 4.7.1 SPFBL you are greylisted.\n\n"; } else if (result.equals("SOFTFAIL") && !Provider.containsHELO(ip, hostname) && Defer.defer(fluxo, Core.getDeferTimeSOFTFAIL())) { Analise.processToday(ip); Analise.processToday(mx); // SOFTFAIL com atrazo programado de 1min. SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "GREYLISTED"); return "action=451 4.7.1 SPFBL you are greylisted.\n\n"; } else { Analise.processToday(ip); Analise.processToday(mx); // Calcula frequencia de consultas. String url = Core.getURL(); String ticket = SPF.addQueryHam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "ACCEPT"); return "action=PREPEND " + "Received-SPFBL: " + result + " " + (url == null ? ticket : url + URLEncoder.encode(ticket, "UTF-8")) + "\n\n"; } } catch (ProcessException ex) { if (ex.isErrorMessage("SPF PARSE")) { return "action=REJECT [SPF] " + "One or more SPF records from " + sender + " " + "could not be interpreted. " + "Please see http://www.openspf.org/SPF_" + "Record_Syntax for details.\n\n"; } else if (ex.isErrorMessage("RESERVED")) { return "action=REJECT [SPF] " + "The domain of " + sender + " is a reserved TLD.\n\n"; } else if (sender == null) { Server.logError(ex); return "action=DEFER [SPF] " + "A transient error occurred. " + "Try again later.\n\n"; } else { return "action=DEFER [SPF] " + "A transient error occurred when " + "checking SPF record from " + sender + ", " + "preventing a result from being reached. " + "Try again later.\n\n"; } } catch (Exception ex) { Server.logError(ex); return "action=WARN SPFBL fatal error.\n\n"; } } } public static String getRecipient(String ticket) throws ProcessException { if (ticket == null) { return null; } else { String registry = Server.decrypt(ticket); int index = registry.indexOf(' '); Date date = getTicketDate(registry.substring(0, index)); if (System.currentTimeMillis() - date.getTime() > 432000000) { // Ticket vencido com mais de 5 dias. throw new ProcessException("TICKET EXPIRED"); } else { registry = registry.substring(index + 1); StringTokenizer tokenizer = new StringTokenizer(registry, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith(">") && Domain.isEmail(token.substring(1))) { return token.substring(1); } } return null; } } } public static String getRecipientURLSafe(String ticket) throws ProcessException { if (ticket == null) { return null; } else { try { byte[] byteArray = Server.decryptToByteArrayURLSafe(ticket); if (byteArray.length > 8) { long date = byteArray[7] & 0xFF; date <<= 8; date += byteArray[6] & 0xFF; date <<= 8; date += byteArray[5] & 0xFF; date <<= 8; date += byteArray[4] & 0xFF; date <<= 8; date += byteArray[3] & 0xFF; date <<= 8; date += byteArray[2] & 0xFF; date <<= 8; date += byteArray[1] & 0xFF; date <<= 8; date += byteArray[0] & 0xFF; if (System.currentTimeMillis() - date > 432000000) { return null; } else { String query = Core.HUFFMAN.decode(byteArray, 8); StringTokenizer tokenizer = new StringTokenizer(query, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith(">")) { token = token.substring(1); if (Domain.isEmail(token)) { return token; } } } return null; } } else { return null; } } catch (ProcessException ex) { return null; } } } public static String getClientURLSafe(String ticket) { if (ticket == null) { return null; } else { try { byte[] byteArray = Server.decryptToByteArrayURLSafe(ticket); if (byteArray.length > 8) { long date = byteArray[7] & 0xFF; date <<= 8; date += byteArray[6] & 0xFF; date <<= 8; date += byteArray[5] & 0xFF; date <<= 8; date += byteArray[4] & 0xFF; date <<= 8; date += byteArray[3] & 0xFF; date <<= 8; date += byteArray[2] & 0xFF; date <<= 8; date += byteArray[1] & 0xFF; date <<= 8; date += byteArray[0] & 0xFF; if (System.currentTimeMillis() - date > 432000000) { return null; } else { String query = Core.HUFFMAN.decode(byteArray, 8); StringTokenizer tokenizer = new StringTokenizer(query, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.endsWith(":")) { int endIndex = token.length() - 1; token = token.substring(0, endIndex); if (Domain.isEmail(token)) { return token; } } } return null; } } else { return null; } } catch (ProcessException ex) { return null; } } } public static String getClient(String ticket) throws ProcessException { if (ticket == null) { return null; } else { String registry = Server.decrypt(ticket); int index = registry.indexOf(' '); Date date = getTicketDate(registry.substring(0, index)); if (System.currentTimeMillis() - date.getTime() > 432000000) { // Ticket vencido com mais de 5 dias. throw new ProcessException("TICKET EXPIRED"); } else { registry = registry.substring(index + 1); StringTokenizer tokenizer = new StringTokenizer(registry, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.endsWith(":")) { int end = token.length() - 1; token = token.substring(0, end); if (Domain.isEmail(token)) { return token; } } } return null; } } } public static String getSender(String ticket) throws ProcessException { if (ticket == null) { return null; } else { String registry = Server.decrypt(ticket); int index = registry.indexOf(' '); Date date = getTicketDate(registry.substring(0, index)); if (System.currentTimeMillis() - date.getTime() > 432000000) { // Ticket vencido com mais de 5 dias. throw new ProcessException("TICKET EXPIRED"); } else { registry = registry.substring(index + 1); StringTokenizer tokenizer = new StringTokenizer(registry, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith("@") && Domain.isHostname(token.substring(1))) { return token; } else if (Domain.isEmail(token)) { return token; } } return null; } } } public static TreeSet<String> getTokenSet(String ticket) throws ProcessException { String registry = Server.decrypt(ticket); int index = registry.indexOf(' '); Date date = getTicketDate(registry.substring(0, index)); if (System.currentTimeMillis() - date.getTime() > 432000000) { // Ticket vencido com mais de 5 dias. throw new ProcessException("TICKET EXPIRED"); } else { TreeSet<String> tokenSet = new TreeSet<String>(); registry = registry.substring(index + 1); StringTokenizer tokenizer = new StringTokenizer(registry, " "); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (isValidReputation(token)) { tokenSet.add(token); } } return tokenSet; } } public static boolean isValidReputation(String token) { if (token == null || token.length() == 0) { return false; } else if (Subnet.isValidIP(token)) { return !Subnet.isReservedIP(token); } else if (token.startsWith(".") && Domain.isHostname(token.substring(1))) { return true; } else if (token.contains("@") && Domain.isEmail(token)) { return true; } else if (token.startsWith("@") && Domain.containsDomain(token.substring(1))) { return true; } else { return false; } } /** * Processa a consulta e retorna o resultado. * * @param query a expresso da consulta. * @return o resultado do processamento. */ protected static String processSPF(InetAddress ipAddress, Client client, User user, String query, LinkedList<User> userList) { try { String result = ""; if (query.length() == 0) { return "INVALID QUERY\n"; } else { String origin; if (client == null) { origin = ipAddress.getHostAddress(); } else if (client.hasEmail()) { origin = ipAddress.getHostAddress() + " " + client.getDomain() + " " + client.getEmail(); } else { origin = ipAddress.getHostAddress() + " " + client.getDomain(); } StringTokenizer tokenizer = new StringTokenizer(query, " "); String firstToken = tokenizer.nextToken(); if (firstToken.equals("SPAM") && tokenizer.countTokens() == 1) { String ticket = tokenizer.nextToken(); TreeSet<String> tokenSet = addComplainURLSafe(origin, ticket, null); if (tokenSet == null) { result = "DUPLICATE COMPLAIN\n"; } else { String userEmail; try { userEmail = SPF.getClientURLSafe(ticket); } catch (Exception ex) { userEmail = client == null ? null : client.getEmail(); } user = User.get(userEmail); if (user != null) { userList.add(user); } String recipient; try { recipient = SPF.getRecipientURLSafe(ticket); } catch (ProcessException ex) { recipient = null; } result = "OK " + tokenSet + (recipient == null ? "" : " >" + recipient) + "\n"; } } else if (firstToken.equals("ABUSE") && tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith("In-Reply-To:") && tokenizer.countTokens() == 1) { token = tokenizer.nextToken(); if (token.startsWith("From:")) { int index = token.indexOf(':') + 1; String recipient = token.substring(index); User recipientUser = User.get(recipient); if (recipientUser == null) { // Se a consulta originar de destinatrio com postmaster cadastrado, // considerar o prprio postmaster como usurio da consulta. index = recipient.indexOf('@'); String postmaster = "postmaster" + recipient.substring(index); User postmasterUser = User.get(postmaster); if (postmasterUser != null) { userList.add(user = postmasterUser); } } else { userList.add(user = recipientUser); } index = query.indexOf(':') + 1; String messageID = query.substring(index); result = "INVALID ID\n"; index = messageID.indexOf('<'); if (index >= 0) { messageID = messageID.substring(index + 1); index = messageID.indexOf('>'); if (index > 0) { messageID = messageID.substring(0, index); result = user.blockByMessageID(messageID) + '\n'; } } } else { result = "INVALID FROM\n"; } } else { result = "INVALID COMMAND\n"; } } else if (firstToken.equals("HOLDING") && tokenizer.countTokens() == 1) { String ticket = tokenizer.nextToken(); result = getHoldStatus(client, ticket, userList) + '\n'; } else if (firstToken.equals("LINK") && tokenizer.hasMoreTokens()) { String ticketSet = tokenizer.nextToken(); TreeSet<String> linkSet = new TreeSet<String>(); while (tokenizer.hasMoreTokens()) { linkSet.add(tokenizer.nextToken()); } StringTokenizer tokenizerTicket = new StringTokenizer(ticketSet, ";"); String unblockURL = null; boolean blocked = false; Action action = null; while (tokenizerTicket.hasMoreTokens()) { String ticket = tokenizerTicket.nextToken(); String userEmail; try { userEmail = SPF.getClientURLSafe(ticket); } catch (Exception ex) { userEmail = client == null ? null : client.getEmail(); } if ((user = User.get(userEmail)) != null) { userList.add(user); long dateTicket = SPF.getDateTicket(ticket); User.Query queryTicket = user.getQuery(dateTicket); if (queryTicket != null) { if (queryTicket.setLinkSet(linkSet)) { SPF.setSpam(dateTicket, queryTicket.getTokenSet()); if (!queryTicket.isWhite() && queryTicket.blockSender(dateTicket)) { Server.logDebug( "new BLOCK '" + queryTicket.getBlockSender() + "' added by LINK."); } action = client == null ? Action.REJECT : client.getActionBLOCK(); unblockURL = queryTicket.getUnblockURL(); blocked = true; } else if (queryTicket.isAnyLinkRED()) { action = client == null ? Action.FLAG : client.getActionRED(); } if (action == Action.HOLD) { queryTicket.setResult("HOLD"); } else if (action == Action.FLAG) { queryTicket.setResult("FLAG"); } else if (action == Action.REJECT) { queryTicket.setResult("REJECT"); } User.storeDB(dateTicket, queryTicket); } } } if (unblockURL != null) { result = "BLOCKED " + unblockURL + "\n"; } else if (blocked) { result = "BLOCKED\n"; } else if (action == Action.HOLD) { result = "HOLD\n"; } else if (action == Action.FLAG) { result = "FLAG\n"; } else if (action == Action.REJECT) { result = "REJECT\n"; } else { result = "CLEAR\n"; } } else if (firstToken.equals("MALWARE") && tokenizer.hasMoreTokens()) { String ticketSet = tokenizer.nextToken(); StringBuilder nameBuilder = new StringBuilder(); while (tokenizer.hasMoreTokens()) { if (nameBuilder.length() > 0) { nameBuilder.append(' '); } nameBuilder.append(tokenizer.nextToken()); } StringBuilder resultBuilder = new StringBuilder(); StringTokenizer ticketTokenizer = new StringTokenizer(ticketSet, ";"); while (ticketTokenizer.hasMoreTokens()) { String ticket = ticketTokenizer.nextToken(); TreeSet<String> tokenSet = addComplainURLSafe(origin, ticket, "MALWARE"); if (tokenSet == null) { resultBuilder.append("DUPLICATE COMPLAIN\n"); } else { // Processar reclamao. String userEmail; try { userEmail = SPF.getClientURLSafe(ticket); } catch (Exception ex) { userEmail = client == null ? null : client.getEmail(); } user = User.get(userEmail); if (user != null) { userList.add(user); long dateTicket = getDateTicket(ticket); User.Query userQuery = user.getQuery(dateTicket); if (userQuery != null && userQuery.setMalware(nameBuilder.toString())) { User.storeDB(dateTicket, userQuery); } } String recipient; try { recipient = SPF.getRecipientURLSafe(ticket); } catch (ProcessException ex) { recipient = null; } // Bloquear automaticamente todos // os tokens com reputao amarela ou vermelha. // Processar reclamao. for (String token : tokenSet) { String block; Status status = SPF.getStatus(token); if (status == Status.RED && (block = Block.add(token)) != null) { Server.logDebug( "new BLOCK '" + block + "' added by '" + recipient + ";MALWARE'."); Peer.sendBlockToAll(block); } if (status != Status.GREEN && !Subnet.isValidIP(token) && (block = Block.addIfNotNull(user, token)) != null) { Server.logDebug( "new BLOCK '" + block + "' added by '" + recipient + ";MALWARE'."); } } resultBuilder.append("OK "); resultBuilder.append(tokenSet); resultBuilder.append(recipient == null ? "" : " >" + recipient); resultBuilder.append("\n"); } } result = resultBuilder.toString(); } else if (firstToken.equals("HEADER") && tokenizer.hasMoreTokens()) { String ticketSet = tokenizer.nextToken(); String key = null; String from = null; String replyto = null; String messageID = null; String unsubscribe = null; String subject = null; while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.startsWith("From:")) { key = "From"; int index = token.indexOf(':'); from = token.substring(index + 1); } else if (token.startsWith("ReplyTo:") || token.startsWith("Reply-To:")) { key = "Reply-To"; int index = token.indexOf(':'); replyto = token.substring(index + 1); } else if (token.startsWith("Message-ID:")) { key = "Message-ID"; int index = token.indexOf(':'); messageID = token.substring(index + 1); } else if (token.startsWith("List-Unsubscribe:")) { key = "List-Unsubscribe"; int index = token.indexOf(':'); unsubscribe = token.substring(index + 1); } else if (token.startsWith("Subject:")) { key = "Subject"; int index = token.indexOf(':'); subject = token.substring(index + 1); } else if (key == null) { from = null; replyto = null; unsubscribe = null; subject = null; break; } else if (key.equals("From")) { from += ' ' + token; } else if (key.equals("Reply-To")) { replyto += ' ' + token; } else if (key.equals("Message-ID")) { messageID += ' ' + token; } else if (key.equals("List-Unsubscribe")) { unsubscribe += ' ' + token; } else if (key.equals("Subject")) { subject += ' ' + token; } } if ((from == null || from.length() == 0) && (replyto == null || replyto.length() == 0) && (messageID == null || messageID.length() == 0) && (unsubscribe == null || unsubscribe.length() == 0) && (subject == null || subject.length() == 0)) { result = "INVALID COMMAND\n"; } else { boolean whitelisted = false; boolean blocklisted = false; TreeSet<String> unblockURLSet = new TreeSet<String>(); StringTokenizer ticketRokenizer = new StringTokenizer(ticketSet, ";"); int n = ticketRokenizer.countTokens(); ArrayList<User.Query> queryList = new ArrayList<User.Query>(n); while (ticketRokenizer.hasMoreTokens()) { String ticket = ticketRokenizer.nextToken(); String userEmail; try { userEmail = SPF.getClientURLSafe(ticket); } catch (Exception ex) { userEmail = client == null ? null : client.getEmail(); } if ((user = User.get(userEmail)) != null) { userList.add(user); long dateTicket = SPF.getDateTicket(ticket); User.Query queryTicket = user.getQuery(dateTicket); if (queryTicket != null) { queryList.add(queryTicket); String resultLocal = queryTicket.setHeader(from, replyto, subject, messageID, unsubscribe); if ("WHITE".equals(resultLocal)) { whitelisted = true; } else if ("BLOCK".equals(resultLocal)) { blocklisted = true; String url = queryTicket.getUnblockURL(); if (url != null) { unblockURLSet.add(url); } } User.storeDB(dateTicket, queryTicket); } } } if (whitelisted) { for (User.Query queryTicket : queryList) { queryTicket.setResult("WHITE"); } result = "WHITE\n"; } else if (blocklisted) { for (User.Query queryTicket : queryList) { queryTicket.setResult("BLOCK"); } if (unblockURLSet.size() == 1) { result = "BLOCKED " + unblockURLSet.first() + "\n"; } else { result = "BLOCKED\n"; } } else { result = "CLEAR\n"; } } } else if (firstToken.equals("HAM") && tokenizer.countTokens() == 1) { String ticket = tokenizer.nextToken(); TreeSet<String> tokenSet = deleteComplainURLSafe(origin, ticket); if (tokenSet == null) { result = "ALREADY REMOVED\n"; } else { String recipient; try { recipient = SPF.getRecipientURLSafe(ticket); } catch (ProcessException ex) { recipient = null; } result = "OK " + tokenSet + (recipient == null ? "" : " >" + recipient) + "\n"; } } else if (firstToken.equals("REFRESH") && tokenizer.countTokens() == 1) { String address = tokenizer.nextToken(); try { if (CacheSPF.refresh(address, true)) { result = "UPDATED\n"; } else { result = "NOT LOADED\n"; } } catch (ProcessException ex) { result = ex.getMessage() + "\n"; } } else if ((firstToken.equals("SPF") && tokenizer.countTokens() >= 4) || tokenizer.countTokens() == 2 || tokenizer.countTokens() == 1 || (firstToken.equals("CHECK") && tokenizer.countTokens() == 4) || (firstToken.equals("CHECK") && tokenizer.countTokens() == 3) || (firstToken.equals("CHECK") && tokenizer.countTokens() == 2)) { try { String ip; String sender; String helo; String recipient; String origem; String fluxo; if (firstToken.equals("SPF")) { // Nova formatao de consulta. ip = tokenizer.nextToken(); sender = tokenizer.nextToken(); while (!sender.endsWith("'") && tokenizer.hasMoreTokens()) { sender += " " + tokenizer.nextToken(); } helo = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : "''"; recipient = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : "''"; ip = ip.substring(1, ip.length() - 1); sender = sender.substring(1, sender.length() - 1); helo = helo.substring(1, helo.length() - 1); if (recipient.equals("'")) { recipient = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : ""; if (recipient.endsWith("'")) { recipient = recipient.substring(0, recipient.length() - 1); } } else { recipient = recipient.substring(1, recipient.length() - 1); } if (sender.length() == 0) { sender = null; } else { sender = sender.toLowerCase(); } recipient = recipient.toLowerCase(); recipient = recipient.replace("\"", ""); } else if (firstToken.equals("CHECK") && tokenizer.countTokens() == 4) { ip = tokenizer.nextToken().toLowerCase(); sender = tokenizer.nextToken().toLowerCase(); helo = tokenizer.nextToken(); recipient = tokenizer.nextToken().toLowerCase(); if (ip.startsWith("'") && ip.endsWith("'")) { ip = ip.substring(1, ip.length() - 1); } if (sender.startsWith("'") && sender.endsWith("'")) { sender = sender.substring(1, sender.length() - 1); } if (helo.startsWith("'") && helo.endsWith("'")) { helo = helo.substring(1, helo.length() - 1); } if (recipient.startsWith("'") && recipient.endsWith("'")) { recipient = recipient.substring(1, recipient.length() - 1); } if (ip.length() == 0) { ip = null; } if (sender.length() == 0) { sender = null; } if (!Domain.isHostname(helo)) { helo = null; } if (recipient.length() == 0) { recipient = null; } else { recipient = recipient.toLowerCase(); } } else { // Manter compatibilidade da verso antiga. // Verso obsoleta. if (firstToken.equals("CHECK")) { ip = tokenizer.nextToken(); } else { ip = firstToken; } if (tokenizer.countTokens() == 2) { sender = tokenizer.nextToken().toLowerCase(); helo = tokenizer.nextToken(); } else { sender = null; helo = tokenizer.nextToken(); } recipient = null; if (ip.startsWith("'") && ip.endsWith("'")) { ip = ip.substring(1, ip.length() - 1); } if (sender != null && sender.startsWith("'") && sender.endsWith("'")) { sender = sender.substring(1, sender.length() - 1); if (sender.length() == 0) { sender = null; } } if (helo.startsWith("'") && helo.endsWith("'")) { helo = helo.substring(1, helo.length() - 1); } } if (!Subnet.isValidIP(ip)) { return "INVALID\n"; } else if (sender != null && !Domain.isEmail(sender)) { return "INVALID\n"; } else if (recipient != null && !Domain.isEmail(recipient)) { return "INVALID\n"; } else if (Subnet.isReservedIP(ip)) { // Message from LAN. return "LAN\n"; } else if (client != null && client.containsFull(ip)) { // Message from LAN. return "LAN\n"; } else { TreeSet<String> tokenSet = new TreeSet<String>(); ip = Subnet.normalizeIP(ip); tokenSet.add(ip); if (Domain.isValidEmail(recipient)) { // Se houver um remetente vlido, // Adicionar no ticket para controle. tokenSet.add('>' + recipient); } if (recipient != null) { User recipientUser = User.get(recipient); if (recipientUser == null) { // Se a consulta originar de destinatrio com postmaster cadastrado, // considerar o prprio postmaster como usurio da consulta. int index = recipient.indexOf('@'); String postmaster = "postmaster" + recipient.substring(index); User postmasterUser = User.get(postmaster); if (postmasterUser != null) { user = postmasterUser; } } else { user = recipientUser; } } if (user != null) { userList.add(user); tokenSet.add(user.getEmail() + ':'); } else if (client != null && client.hasEmail()) { tokenSet.add(client.getEmail() + ':'); } // Passar a acompanhar todos os // HELO quando apontados para o IP para // uma nova forma de interpretar dados. String hostname; if (CacheHELO.match(ip, helo, false)) { hostname = Domain.normalizeHostname(helo, true); } else { hostname = Reverse.getHostname(ip); hostname = Domain.normalizeHostname(hostname, true); } if (hostname == null) { Server.logDebug("no rDNS for " + ip + "."); } else if (Domain.isOfficialTLD(hostname)) { return "INVALID\n"; } else { // Verificao de pilha dupla, // para pontuao em ambas pilhas. String ipv4 = CacheHELO.getUniqueIPv4(hostname); String ipv6 = CacheHELO.getUniqueIPv6(hostname); if (ip.equals(ipv6) && CacheHELO.match(ipv4, hostname, false)) { // Equivalncia de pilha dupla se // IPv4 for nico para o hostname. tokenSet.add(ipv4); } else if (ip.equals(ipv4) && CacheHELO.match(ipv6, hostname, false)) { // Equivalncia de pilha dupla se // IPv6 for nico para o hostname. tokenSet.add(ipv6); } } if (Generic.containsGenericSoft(hostname)) { // Quando o reverso for // genrico, no consider-lo. hostname = null; } else if (hostname != null) { tokenSet.add(hostname); } LinkedList<String> logList = null; if (sender != null && firstToken.equals("CHECK")) { int index = sender.lastIndexOf('@'); String domain = sender.substring(index + 1); logList = new LinkedList<String>(); try { CacheSPF.refresh(domain, false); } catch (ProcessException ex) { logList.add("Cannot refresh SPF registry: " + ex.getErrorMessage()); logList.add("Using cached SPF registry."); } } SPF spf; if (sender == null) { spf = null; result = "NONE"; } else if (Domain.isOfficialTLD(sender)) { spf = null; result = "NONE"; } else if (Generic.containsGeneric(sender)) { spf = null; result = "NONE"; } else if ((spf = CacheSPF.get(sender)) == null) { result = "NONE"; } else if (spf.isInexistent()) { result = "NONE"; } else { result = spf.getResult(ip, sender, helo, logList); } String mx = Domain.extractHost(sender, true); if (user != null && user.isLocal()) { // Message from local user. return "LAN\n"; } else if (recipient != null && result.equals("PASS")) { if (recipient.endsWith(mx)) { // Message from same domain. return "LAN\n"; } else if (recipient.equals(Core.getAbuseEmail()) && User.exists(sender, "postmaster" + mx)) { // Message to abuse. return "LAN\n"; } } if (result.equals("PASS") || (sender != null && Provider.containsHELO(ip, hostname))) { // Quando fo PASS, significa que o domnio // autorizou envio pelo IP, portanto o dono dele // responsavel pelas mensagens. if (!Provider.containsExact(mx)) { // No um provedor ento // o MX deve ser listado. tokenSet.add(mx); origem = mx; } else if (Domain.isValidEmail(sender)) { // Listar apenas o remetente se o // hostname for um provedor de e-mail. String userEmail = null; String recipientEmail = null; for (String token : tokenSet) { if (token.endsWith(":")) { userEmail = token; } else if (token.startsWith(">")) { recipientEmail = token; } } tokenSet.clear(); tokenSet.add(sender); if (userEmail != null) { tokenSet.add(userEmail); } if (recipientEmail != null) { tokenSet.add(recipientEmail); } origem = sender; } else { origem = sender; } fluxo = origem + ">" + recipient; } else if (hostname == null) { origem = (sender == null ? "" : sender + '>') + ip; fluxo = origem + ">" + recipient; } else { String dominio = Domain.extractDomain(hostname, true); origem = (sender == null ? "" : sender + '>') + (dominio == null ? hostname : dominio.substring(1)); fluxo = origem + ">" + recipient; } Long recipientTrapTime = Trap.getTimeRecipient(client, user, recipient); if (firstToken.equals("CHECK")) { String results = "\nSPF resolution results:\n"; if (spf != null && spf.isInexistent()) { results += " NXDOMAIN\n"; } else if (logList == null || logList.isEmpty()) { results += " NONE\n"; } else { for (String log : logList) { results += " " + log + "\n"; } } String white; String block; if ((white = White.find(client, user, ip, sender, hostname, result, recipient)) != null) { results += "\nFirst WHITE match: " + white + "\n"; } else if ((block = Block.find(client, user, ip, sender, hostname, result, recipient, false, true, true, false)) != null) { results += "\nFirst BLOCK match: " + block + "\n"; } TreeSet<String> graceSet = new TreeSet<String>(); if (Domain.isGraceTime(sender)) { graceSet.add(Domain.extractDomain(sender, false)); } if (Domain.isGraceTime(hostname)) { graceSet.add(Domain.extractDomain(hostname, false)); } if (!graceSet.isEmpty()) { results += "\n"; results += "Domains in grace time:\n"; for (String grace : graceSet) { results += " " + grace + "\n"; } } results += "\n"; results += "Considered identifiers and status:\n"; tokenSet = expandTokenSet(tokenSet); TreeMap<String, Distribution> distributionMap = CacheDistribution.getMap(tokenSet); int count = 0; for (String token : tokenSet) { if (!token.startsWith(">") && !token.endsWith(":")) { if (!Ignore.contains(token)) { float probability; Status status; if (distributionMap.containsKey(token)) { Distribution distribution = distributionMap.get(token); probability = distribution.getSpamProbability(token); status = distribution.getStatus(token); } else { probability = 0.0f; status = SPF.Status.GREEN; } results += " " + token + " " + status.name() + " " + Core.DECIMAL_FORMAT.format(probability) + "\n"; count++; } } } if (count == 0) { results += " NONE\n"; } results += "\n"; return results; } else if (recipientTrapTime == null && White.contains(client, user, ip, sender, hostname, result, recipient)) { if (White.contains(client, user, ip, sender, hostname, result, null)) { // Limpa da lista BLOCK um possvel falso positivo. Block.clear(client, user, ip, sender, hostname, result, null); } // Calcula frequencia de consultas. String url = Core.getURL(); String ticket = SPF.addQueryHam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "WHITE"); return "WHITE " + (url == null ? ticket : url + ticket) + "\n"; } else if (Block.contains(client, user, ip, sender, hostname, result, recipient, true, true, true, true)) { Action action = client == null ? Action.REJECT : client.getActionBLOCK(); if (action == Action.REJECT) { // Calcula frequencia de consultas. long time = Server.getNewUniqueTime(); User.Query queryLocal = SPF.addQuerySpam(time, client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "BLOCK"); action = client == null ? Action.FLAG : client.getActionRED(); if (action != Action.REJECT && queryLocal != null && queryLocal.needHeader()) { if (action == Action.FLAG) { queryLocal.setResult("FLAG"); String url = Core.getURL(); String ticket = SPF.createTicket(time, tokenSet); return "FLAG " + (url == null ? ticket : url + ticket) + "\n"; } else if (action == Action.HOLD) { queryLocal.setResult("HOLD"); String url = Core.getURL(); String ticket = SPF.createTicket(time, tokenSet); return "HOLD " + (url == null ? ticket : url + ticket) + "\n"; } else { return "ERROR: UNDEFINED ACTION\n"; } } else { String url = Core.getUnblockURL(client, user, ip, sender, hostname, recipient); if (url == null) { return "BLOCKED\n"; } else { return "BLOCKED " + url + "\n"; } } } else if (action == Action.FLAG) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FLAG"); return "FLAG " + (url == null ? ticket : url + ticket) + "\n"; } else if (action == Action.HOLD) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "HOLD"); return "HOLD " + (url == null ? ticket : url + ticket) + "\n"; } else { return "ERROR: UNDEFINED ACTION\n"; } } else if (Generic.containsDynamicDomain(hostname)) { // Bloquear automaticamente range de IP dinmico. String cidr = Subnet .normalizeCIDR(SubnetIPv4.isValidIPv4(ip) ? ip + "/24" : ip + "/48"); if (Block.tryOverlap(cidr)) { Server.logDebug( "new BLOCK '" + cidr + "' added by '" + hostname + ";DYNAMIC'."); } else if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + hostname + ";DYNAMIC'."); } SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "INVALID\n"; } else if (spf != null && spf.isDefinitelyInexistent()) { // Bloquear automaticamente IP com reputao vermelha. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + mx + ";NXDOMAIN'."); } } Analise.processToday(ip); // O domnio foi dado como inexistente inmeras vezes. // Rejeitar e denunciar o host pois h abuso de tentativas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "NXDOMAIN"); return "NXDOMAIN\n"; } else if (spf != null && spf.isInexistent()) { Analise.processToday(ip); SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "NXDOMAIN"); return "NXDOMAIN\n"; } else if (result.equals("FAIL")) { // Bloquear automaticamente IP com reputao vermelha. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by '" + sender + ";FAIL'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FAIL"); // Retornar FAIL somente se no houver // liberao literal do remetente com FAIL. return "FAIL\n"; } else if (sender != null && !Domain.isEmail(sender)) { // Bloquear automaticamente IP com reputao vermelha. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug( "new BLOCK '" + ip + "' added by '" + sender + ";INVALID'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "INVALID\n"; } else if (sender != null && Domain.isOfficialTLD(sender)) { // Bloquear automaticamente IP com reputao vermelha. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug( "new BLOCK '" + ip + "' added by '" + sender + ";RESERVED'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "INVALID\n"; } else if (sender == null && !CacheHELO.match(ip, hostname, false)) { // Bloquear automaticamente IP com reputao ruim. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by 'INVALID'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); // HELO invlido sem remetente. return "INVALID\n"; } else if (hostname == null && Core.isReverseRequired()) { if (Block.tryAdd(ip)) { Server.logDebug("new BLOCK '" + ip + "' added by 'NONE'."); } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); // Require a valid HELO or reverse. return "INVALID\n"; } else if (recipient != null && !Domain.isValidEmail(recipient)) { Analise.processToday(ip); Analise.processToday(mx); SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INEXISTENT"); return "INEXISTENT\n"; } else if (recipientTrapTime != null) { if (System.currentTimeMillis() > recipientTrapTime) { // Spamtrap for (String token : tokenSet) { String block; Status status = SPF.getStatus(token); if (status == Status.RED && (block = Block.add(token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + recipient + ";SPAMTRAP'."); Peer.sendBlockToAll(block); } if (status != Status.GREEN && !Subnet.isValidIP(token) && (block = Block.addIfNotNull(user, token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + recipient + ";SPAMTRAP'."); } } Analise.processToday(ip); Analise.processToday(mx); // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "TRAP"); return "SPAMTRAP\n"; } else { // Inexistent for (String token : tokenSet) { String block; Status status = SPF.getStatus(token); if (status == Status.RED && (block = Block.add(token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + recipient + ";INEXISTENT'."); Peer.sendBlockToAll(block); } if (status != Status.GREEN && !Subnet.isValidIP(token) && (block = Block.addIfNotNull(user, token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + recipient + ";INEXISTENT'."); } } Analise.processToday(ip); Analise.processToday(mx); SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INEXISTENT"); return "INEXISTENT\n"; } } else if (Defer.count(fluxo) > Core.getFloodMaxRetry()) { Analise.processToday(ip); Analise.processToday(mx); // A origem atingiu o limite de atraso // para liberao do destinatrio. long time = System.currentTimeMillis(); Defer.end(fluxo); Server.logDefer(time, fluxo, "DEFER FLOOD"); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "BLOCKED\n"; } else if (!result.equals("PASS") && !CacheHELO.match(ip, hostname, false)) { // Bloquear automaticamente IP com reputao amarela. if (SPF.isRed(ip)) { if (Block.tryAdd(ip)) { Server.logDebug( "new BLOCK '" + ip + "' added by '" + recipient + ";INVALID'."); } } Analise.processToday(ip); SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "INVALID"); return "INVALID\n"; } else if (recipient != null && recipient.startsWith("postmaster@")) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "ACCEPT"); return result + " " + (url == null ? ticket : url + URLEncoder.encode(ticket, "UTF-8")) + "\n"; } else if (result.equals("PASS") && SPF.isGood(Provider.containsExact(mx) ? sender : mx)) { // O remetente vlido e tem excelente reputao, // ainda que o provedor dele esteja com reputao ruim. String url = Core.getURL(); String ticket = SPF.addQueryHam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "ACCEPT"); return "PASS " + (url == null ? ticket : url + URLEncoder.encode(ticket, "UTF-8")) + "\n"; } else if (SPF.hasRed(tokenSet) || Analise.isCusterRED(ip, sender, hostname)) { Analise.processToday(ip); Analise.processToday(mx); Action action = client == null ? Action.REJECT : client.getActionRED(); if (action == Action.REJECT) { // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "BLOCKED\n"; } else if (action == Action.DEFER) { if (Defer.defer(fluxo, Core.getDeferTimeRED())) { String url = Core.getReleaseURL(fluxo); SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "LISTED"); if (url == null || Defer.count(fluxo) > 1) { return "LISTED\n"; } else if (result.equals("PASS") && enviarLiberacao(url, sender, recipient)) { // Envio da liberao por e-mail se // houver validao do remetente por PASS. return "LISTED\n"; } else { return "LISTED " + url + "\n"; } } else { // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "BLOCKED\n"; } } else if (action == Action.FLAG) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FLAG"); return "FLAG " + (url == null ? ticket : url + ticket) + "\n"; } else if (action == Action.HOLD) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "HOLD"); return "HOLD " + (url == null ? ticket : url + ticket) + "\n"; } else { return "ERROR: UNDEFINED ACTION\n"; } } else if (Domain.isGraceTime(sender) || Domain.isGraceTime(hostname)) { Server.logTrace("domain in grace time."); for (String token : tokenSet) { String block; Status status = SPF.getStatus(token); if (status == Status.RED && (block = Block.add(token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + status + "'."); Peer.sendBlockToAll(block); } if (status != Status.GREEN && !Subnet.isValidIP(token) && (block = Block.addIfNotNull(user, token)) != null) { Server.logDebug("new BLOCK '" + block + "' added by '" + status + "'."); } } Analise.processToday(ip); Analise.processToday(mx); Action action = client == null ? Action.REJECT : client.getActionGRACE(); if (action == Action.REJECT) { // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "BLOCKED\n"; } else if (action == Action.DEFER) { if (Defer.defer(fluxo, Core.getDeferTimeRED())) { String url = Core.getReleaseURL(fluxo); SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "LISTED"); if (url == null || Defer.count(fluxo) > 1) { return "LISTED\n"; } else if (result.equals("PASS") && enviarLiberacao(url, sender, recipient)) { // Envio da liberao por e-mail se // houver validao do remetente por PASS. return "LISTED\n"; } else { return "LISTED " + url + "\n"; } } else { // Calcula frequencia de consultas. SPF.addQuerySpam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "REJECT"); return "BLOCKED\n"; } } else if (action == Action.FLAG) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "FLAG"); return "FLAG " + (url == null ? ticket : url + ticket) + "\n"; } else if (action == Action.HOLD) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "HOLD"); return "HOLD " + (url == null ? ticket : url + ticket) + "\n"; } else { return "ERROR: UNDEFINED ACTION\n"; } } else if (SPF.hasYellow(tokenSet) && Defer.defer(fluxo, Core.getDeferTimeYELLOW())) { Analise.processToday(ip); Analise.processToday(mx); Action action = client == null ? Action.DEFER : client.getActionYELLOW(); if (action == Action.DEFER) { // Pelo menos um identificador do conjunto est em greylisting com atrazo de 10min. SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "GREYLIST"); return "GREYLIST\n"; } else if (action == Action.HOLD) { String url = Core.getURL(); String ticket = SPF.getTicket(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "HOLD"); return "HOLD " + (url == null ? ticket : url + ticket) + "\n"; } else { return "ERROR: UNDEFINED ACTION\n"; } } else if (SPF.isFlood(tokenSet) && !Provider.containsHELO(ip, hostname) && Defer.defer(origem, Core.getDeferTimeFLOOD())) { Analise.processToday(ip); Analise.processToday(mx); // Pelo menos um identificador est com frequncia superior ao permitido. Server.logDebug("FLOOD " + tokenSet); SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "GREYLIST"); return "GREYLIST\n"; } else if (result.equals("SOFTFAIL") && !Provider.containsHELO(ip, hostname) && Defer.defer(fluxo, Core.getDeferTimeSOFTFAIL())) { Analise.processToday(ip); Analise.processToday(mx); // SOFTFAIL com atrazo de 1min. SPF.addQuery(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "GREYLIST"); return "GREYLIST\n"; } else { Analise.processToday(ip); Analise.processToday(mx); // Calcula frequencia de consultas. String url = Core.getURL(); String ticket = SPF.addQueryHam(client, user, ip, helo, hostname, sender, result, recipient, tokenSet, "ACCEPT"); return result + " " + (url == null ? ticket : url + URLEncoder.encode(ticket, "UTF-8")) + "\n"; } } } catch (ProcessException ex) { if (ex.isErrorMessage("HOST NOT FOUND")) { return "NXDOMAIN\n"; } else { throw ex; } } } else { return "INVALID QUERY\n"; } } return result; } catch (ProcessException ex) { Server.logError(ex); return ex.getMessage() + "\n"; } catch (Exception ex) { Server.logError(ex); return "ERROR: FATAL\n"; } } private static boolean enviarLiberacao(String url, String remetente, String destinatario) { if (Core.hasOutputSMTP() && Core.hasAdminEmail() && Domain.isValidEmail(remetente) && Domain.isValidEmail(destinatario) && url != null && !NoReply.contains(remetente, true)) { try { Server.logDebug("sending liberation by e-mail."); Locale locale = Core.getDefaultLocale(remetente); InternetAddress[] recipients = InternetAddress.parse(remetente); Properties props = System.getProperties(); Session session = Session.getDefaultInstance(props); MimeMessage message = new MimeMessage(session); message.setHeader("Date", Core.getEmailDate()); message.setFrom(Core.getAdminEmail()); message.addRecipients(Message.RecipientType.TO, recipients); String subject; if (locale.getLanguage().toLowerCase().equals("pt")) { subject = "Liberao de recebimento"; } else { subject = "Receiving release"; } message.setSubject(subject); // Corpo da mensagem. StringBuilder builder = new StringBuilder(); builder.append("<!DOCTYPE html>\n"); builder.append("<html lang=\""); builder.append(locale.getLanguage()); builder.append("\">\n"); builder.append(" <head>\n"); builder.append(" <meta charset=\"UTF-8\">\n"); builder.append(" <title>"); builder.append(subject); builder.append("</title>\n"); ServerHTTP.loadStyleCSS(builder); builder.append(" </head>\n"); builder.append(" <body>\n"); builder.append(" <div id=\"container\">\n"); builder.append(" <div id=\"divlogo\">\n"); builder.append(" <img src=\"cid:logo\">\n"); builder.append(" </div>\n"); ServerHTTP.buildMessage(builder, subject); if (locale.getLanguage().toLowerCase().equals("pt")) { ServerHTTP.buildText(builder, "O recebimento da sua mensagem para " + destinatario + " est sendo atrasado por suspeita de SPAM."); ServerHTTP.buildText(builder, "Para que sua mensagem seja liberada, acesse este link e resolva o desafio reCAPTCHA:"); } else { ServerHTTP.buildText(builder, "Receiving your message to " + destinatario + " is being delayed due to suspected SPAM."); ServerHTTP.buildText(builder, "In order for your message to be released, access this link and resolve the reCAPTCHA:"); } ServerHTTP.buildText(builder, "<a href=\"" + url + "\">" + url + "</a>"); ServerHTTP.buildFooter(builder, locale); builder.append(" </div>\n"); builder.append(" </body>\n"); builder.append("</html>\n"); // Making HTML part. MimeBodyPart htmlPart = new MimeBodyPart(); htmlPart.setContent(builder.toString(), "text/html;charset=UTF-8"); // Making logo part. MimeBodyPart logoPart = new MimeBodyPart(); File logoFile = ServerHTTP.getWebFile("logo.png"); logoPart.attachFile(logoFile); logoPart.setContentID("<logo>"); logoPart.addHeader("Content-Type", "image/png"); logoPart.setDisposition(MimeBodyPart.INLINE); // Join both parts. MimeMultipart content = new MimeMultipart("related"); content.addBodyPart(htmlPart); content.addBodyPart(logoPart); // Set multiplart content. message.setContent(content); message.saveChanges(); // Enviar mensagem. return Core.sendMessage(message, 5000); } catch (MailConnectException ex) { return false; } catch (SendFailedException ex) { return false; } catch (Exception ex) { Server.logError(ex); return false; } } else { return false; } } public static String createTicket(long time, TreeSet<String> tokenSet) throws ProcessException { String ticket = "spam"; for (String token : tokenSet) { ticket += " " + token; } byte[] byteArray = Core.HUFFMAN.encodeByteArray(ticket, 8); byteArray[0] = (byte) (time & 0xFF); byteArray[1] = (byte) ((time = time >>> 8) & 0xFF); byteArray[2] = (byte) ((time = time >>> 8) & 0xFF); byteArray[3] = (byte) ((time = time >>> 8) & 0xFF); byteArray[4] = (byte) ((time = time >>> 8) & 0xFF); byteArray[5] = (byte) ((time = time >>> 8) & 0xFF); byteArray[6] = (byte) ((time = time >>> 8) & 0xFF); byteArray[7] = (byte) ((time >>> 8) & 0xFF); return Server.encryptURLSafe(byteArray); } // public static String createTicket(TreeSet<String> tokenSet) throws ProcessException { // String ticket = Server.getNewTicketDate(); // for (String token : tokenSet) { // ticket += " " + token; // } // return Server.encrypt(ticket); // } private static Date getTicketDate(String date) throws ProcessException { try { return Server.parseTicketDate(date); } catch (ParseException ex) { throw new ProcessException("ERROR: INVALID TICKET", ex); } } public static TreeSet<String> expandTokenSet(TreeSet<String> tokenSet) { TreeSet<String> expandedSet = new TreeSet<String>(); for (String token : tokenSet) { if (token != null) { expandedSet.add(token); boolean expandDomain; if (token.startsWith("@") && Domain.isHostname(token.substring(1))) { token = '.' + token.substring(1); expandDomain = true; } else if (!token.startsWith("@") && Domain.isEmail(token)) { expandDomain = false; } else if (token.startsWith(".") && Domain.isHostname(token.substring(1))) { expandDomain = true; } else if (!token.startsWith(".") && Domain.isHostname(token)) { token = '.' + token; expandDomain = true; } else { expandDomain = false; } if (expandDomain) { try { String dominio = Domain.extractDomain(token, true); expandedSet.add(dominio); } catch (ProcessException ex) { if (!ex.isErrorMessage("RESERVED")) { Server.logError(ex); } } } } } return expandedSet; } public static String getTicket(Client client, User user, String ip, String helo, String hostname, String sender, String qualifier, String recipient, TreeSet<String> tokenSet, String result) throws ProcessException { long time = Server.getNewUniqueTime(); if (user != null) { user.addQuery(time, client, ip, helo, hostname, sender, qualifier, recipient, tokenSet, result); } return SPF.createTicket(time, tokenSet); } public static String getTicket(TreeSet<String> tokenSet) throws ProcessException { long time = Server.getNewUniqueTime(); return SPF.createTicket(time, tokenSet); } public static String addQueryHam(Client client, User user, String ip, String helo, String hostname, String sender, String qualifier, String recipient, TreeSet<String> tokenSet, String result) throws ProcessException { long time = Server.getNewUniqueTime(); String ticket = SPF.createTicket(time, tokenSet); for (String token : expandTokenSet(tokenSet)) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, true); distribution.addQueryHam(time); distribution.getStatus(token); } } if (user != null) { user.addQuery(time, client, ip, helo, hostname, sender, qualifier, recipient, tokenSet, result); } return ticket; } public static String addQueryHam(TreeSet<String> tokenSet) throws ProcessException { long time = Server.getNewUniqueTime(); String ticket = SPF.createTicket(time, tokenSet); for (String token : expandTokenSet(tokenSet)) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, true); distribution.addQueryHam(time); distribution.getStatus(token); } } return ticket; } public static boolean setHam(long time, TreeSet<String> tokenSet) { boolean modified = false; for (String token : expandTokenSet(tokenSet)) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, true); modified |= distribution.addHam(time); distribution.getStatus(token); } } return modified; } public static boolean setSpam(long time, TreeSet<String> tokenSet) { boolean modified = false; for (String token : expandTokenSet(tokenSet)) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, true); modified |= distribution.addSpam(time); distribution.getStatus(token); } } return modified; } public static User.Query addQuerySpam(Client client, User user, String ip, String helo, String hostname, String sender, String qualifier, String recipient, TreeSet<String> tokenSet, String result) throws ProcessException { long time = Server.getNewUniqueTime(); return addQuerySpam(time, client, user, ip, helo, hostname, sender, qualifier, recipient, tokenSet, result); } public static User.Query addQuerySpam(long time, Client client, User user, String ip, String helo, String hostname, String sender, String qualifier, String recipient, TreeSet<String> tokenSet, String result) throws ProcessException { for (String token : expandTokenSet(tokenSet)) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, true); if (Ignore.contains(token)) { distribution.addQueryHam(time); distribution.getStatus(token); } else { distribution.addQuerySpam(time); distribution.getStatus(token); Peer.sendToAll(token, distribution); } } } if (user == null) { return null; } else { return user.addQuery(time, client, ip, helo, hostname, sender, qualifier, recipient, tokenSet, result); } } public static void addQuery(Client client, User user, String ip, String helo, String hostname, String sender, String qualifier, String recipient, TreeSet<String> tokenSet, String result) throws ProcessException { long time = Server.getNewUniqueTime(); if (user != null) { user.addQuery(time, client, ip, helo, hostname, sender, qualifier, recipient, tokenSet, result); } } public static void addQuerySpam(TreeSet<String> tokenSet) { long time = Server.getNewUniqueTime(); for (String token : expandTokenSet(tokenSet)) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, true); if (Ignore.contains(token)) { distribution.addQueryHam(time); distribution.getStatus(token); } else { distribution.addQuerySpam(time); distribution.getStatus(token); Peer.sendToAll(token, distribution); } } } } public static void createDistribution(String token) { Distribution distribution = CacheDistribution.get(token, true); distribution.getStatus(token); } public static Status getStatus(String token, boolean refresh) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { return Status.GREEN; } else if (refresh) { return distribution.getStatus(token); } else { return distribution.getStatus(); } } public static boolean isGood(String token) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { return false; } else { return distribution.isGood(); } } public static Status getStatus(String token) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { return Status.GREEN; } else { return distribution.getStatus(token); } } else { return Status.GREEN; } } public static boolean isRed(String token) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { return false; } else { return distribution.isRed(token); } } else { return false; } } public static boolean isYellow(String token) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { // Distribuio no encontrada. // Considerar que no est listado. return false; } else { return distribution.isYellow(token); } } else { return false; } } public static boolean isGreen(String token) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { // Distribuio no encontrada. // Considerar que no est listado. return true; } else { return distribution.isGreen(token); } } else { return false; } } public static boolean isNotGreen(String token) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { // Distribuio no encontrada. // Considerar que no est listado. return false; } else { return distribution.isNotGreen(token); } } else { return false; } } public static boolean isGreen(String token, boolean refresh) { if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { return true; } else if (refresh) { return distribution.isGreen(token); } else { return distribution.isGreen(); } } else { return false; } } // public static boolean isBlocked(String token) { // if (CacheComplain.isValid(token)) { // Distribution distribution = CacheDistribution.get(token, true); // return distribution.isBlocked(token); // } else { // return false; // } // } public static boolean isFlood(String token) { if (Ignore.contains(token)) { return false; } else if (isValidReputation(token)) { Distribution distribution = CacheDistribution.get(token, false); if (distribution == null) { // Distribuio no encontrada. // Considerar que no rajada. return false; } else { return distribution.isFlood(token); } } else { return false; } } public static boolean isGreen(TreeSet<String> tokenSet) { for (String token : expandTokenSet(tokenSet)) { if (isNotGreen(token)) { return false; } } return true; } public static boolean hasYellow(TreeSet<String> tokenSet) { for (String token : expandTokenSet(tokenSet)) { if (isYellow(token)) { return true; } } return false; } public static boolean hasRed(TreeSet<String> tokenSet) { for (String token : expandTokenSet(tokenSet)) { if (isRed(token)) { return true; } } return false; } private static boolean isFlood(TreeSet<String> tokenSet) throws ProcessException { for (String token : expandTokenSet(tokenSet)) { if (isFlood(token) && !Ignore.contains(token)) { return true; } } return false; } /** * Enumerao do status da distribuio. */ public enum Status implements Serializable { GRAY, // Obsoleto BLACK, // Obsoleto WHITE, // Whitelisted GREEN, YELLOW, RED, BLOCK; // Blocked. } /** * Constantes que determinam os limiares de listagem. */ private static final float LIMIAR1 = 0.25f; private static final float LIMIAR2 = 0.50f; private static final float LIMIAR3 = 0.75f; /** * Classe que representa a distribuio binomial entre SPAM e HAM. */ public static final class Distribution implements Serializable { private static final long serialVersionUID = 1L; private long lastQuery; // ltima consulta distribuio. private Status status; // Status atual da distribuio. private NormalDistribution frequency = null; // Frequncia mdia em segundos. private TreeSet<Long> hamSet = new TreeSet<Long>(); private TreeSet<Long> spamSet = new TreeSet<Long>(); private boolean ready = false; private boolean good = false; public Distribution() { lastQuery = 0; status = Status.GREEN; CacheDistribution.CHANGED = true; } public synchronized void reset() { hamSet.clear(); spamSet.clear(); lastQuery = 0; status = Status.GREEN; frequency = null; CacheDistribution.CHANGED = true; } public synchronized Distribution replicate() { Distribution clone = new Distribution(); clone.lastQuery = this.lastQuery; clone.status = this.status; clone.frequency = this.frequency == null ? null : this.frequency.replicate(); clone.hamSet.addAll(this.hamSet); clone.spamSet.addAll(this.spamSet); clone.ready = this.ready; return clone; } public boolean isExpired7() { return System.currentTimeMillis() - lastQuery > 604800000; } public boolean isExpired14() { return System.currentTimeMillis() - lastQuery > 604800000 * 2; } public synchronized boolean dropExpiredQuery() { long time = System.currentTimeMillis() - 604800000; TreeSet<Long> removeSet = new TreeSet<Long>(); removeSet.addAll(hamSet.headSet(time)); removeSet.addAll(spamSet.headSet(time)); boolean hamChanged = hamSet.removeAll(removeSet); boolean spamChanged = spamSet.removeAll(removeSet); ready |= hamChanged; hairCut(); return hamChanged || spamChanged; } public int getTotalSize() { return hamSet.size() + spamSet.size(); } public synchronized boolean clear() { hamSet.addAll(spamSet); spamSet.clear(); status = Status.GREEN; CacheDistribution.CHANGED = true; return true; } public boolean hasFrequency() { return frequency != null; } public Double getFrequencyMin() { if (frequency == null) { return null; } else { return frequency.getMinimum(); } } public boolean hasLastQuery() { return lastQuery > 0; } public long getIdleTimeMillis() { if (lastQuery == 0) { return 0; } else { return System.currentTimeMillis() - lastQuery; } } public String getFrequencyLiteral() { if (hasFrequency()) { int frequencyInt = frequency.getMaximumInt(); long idleTimeInt = getIdleTimeMillis(); if (idleTimeInt > frequencyInt * 5 && idleTimeInt > 3600000) { return "DEAD"; } else { char sinal = '~'; if (idleTimeInt > frequencyInt * 3) { sinal = '>'; } if (frequencyInt >= 3600000) { return sinal + ((frequencyInt / 3600000) + "h"); } else if (frequencyInt >= 60000) { return sinal + ((frequencyInt / 60000) + "min"); } else if (frequencyInt >= 1000) { return sinal + ((frequencyInt / 1000) + "s"); } else { return sinal + (frequencyInt + "ms"); } } } else { return "UNDEFINED"; } } private float getInterval(long currentTime, boolean refresh) { float interval; if (lastQuery == 0) { interval = 0; } else { interval = (float) (currentTime - lastQuery) / (float) 1000; } if (refresh) { lastQuery = currentTime; CacheDistribution.CHANGED = true; } return interval; } public void addQueryHam(long time) { addHam(time); float interval = getInterval(time, true); if (interval == 0.0f) { // Se no houver intervalo definido, // considerar frequncia nula. frequency = null; } else if (frequency == null) { frequency = new NormalDistribution(interval); } else { frequency.addElement(interval); } } public void addQuerySpam(long time) { addSpam(time); float interval = getInterval(time, true); if (interval == 0.0f) { // Se no houver intervalo definido, // considerar frequncia nula. frequency = null; } else if (frequency == null) { frequency = new NormalDistribution(interval); } else { frequency.addElement(interval); } } public synchronized int getHAM() { return hamSet.size(); } public synchronized int getSPAM() { return spamSet.size(); } public synchronized float getSpamProbability() { int ham = hamSet.size(); int spam = spamSet.size(); if (ham + spam == 0) { return 0.0f; } else { return (float) spam / (float) (ham + spam); } } public float getSpamProbability(String token) { int[] binomial = getBinomial(); if (token != null) { for (Peer peer : Peer.getSet()) { short reputationMax = peer.getReputationMax(); if (reputationMax > 0) { Peer.Binomial peerBinomial = peer.getReputation(token); if (peerBinomial != null) { int hamInt = peerBinomial.getHAM(); int spamInt = peerBinomial.getSPAM(); int total = hamInt + spamInt; if (total > reputationMax) { float proporcion = (float) reputationMax / total; hamInt = (int) (hamInt * proporcion); spamInt = (int) (spamInt * proporcion); } binomial[0] += hamInt; binomial[1] += spamInt; } } } } int total = binomial[0] + binomial[1]; float probability = (float) binomial[1] / (float) total; good = binomial[0] > 512 && binomial[1] < 32; if (total == 0) { return 0.0f; } else if (probability > LIMIAR1 && binomial[1] < 5) { // Quantidade pequena para dar preciso. return LIMIAR1; } else if (probability > LIMIAR2 && binomial[1] < 7) { // Quantidade pequena para dar preciso. // Para haver bloqueio temporrio // necessrio pelo meno cinco denuncias. return LIMIAR2; } else if (probability > LIMIAR3 && binomial[1] < 13) { // Quantidade pequena para dar preciso. // Para haver bloqueio definitivo // necessrio pelo meno sete denuncias. return LIMIAR3; } else { return probability; } } public Status getStatus() { return status; } /** * Mquina de estados para listar em um pico e retirar a listagem * somente quando o total cair consideravelmente aps este pico. * * @return o status atual da distribuio. */ public Status getStatus(String token) { Status statusOld = status; float probability = getSpamProbability(token); if (probability < LIMIAR1) { status = Status.GREEN; } else if (probability > LIMIAR2) { status = Status.RED; } else if (statusOld == Status.RED) { status = Status.RED; } else { status = Status.YELLOW; } return status; } public boolean isGood() { return good; } public boolean isGreen() { return status == Status.GREEN; } public boolean isRed() { return status == Status.RED; } public boolean isRed(String token) { return getStatus(token) == Status.RED; } public boolean isGreen(String token) { return getStatus(token) == Status.GREEN; } public boolean isNotGreen(String token) { return getStatus(token) != Status.GREEN; } public boolean isYellow(String token) { return getStatus(token) == Status.YELLOW; } // public boolean isBlocked(String token) { // return getStatus(token) == Status.BLOCK; // } public boolean isFlood(String token) { Double frequency = getFrequencyMin(); if (frequency == null) { return false; } else if (Subnet.isValidIP(token)) { return frequency < Core.getFloodTimeIP(); } else if (Domain.isEmail(token)) { return frequency < Core.getFloodTimeSender(); } else { return frequency < Core.getFloodTimeHELO(); } } public synchronized boolean removeSpam(long time) { hamSet.add(time); spamSet.remove(time); CacheDistribution.CHANGED = true; return true; } public void hairCut() { while (getTotalSize() > Core.getReputationLimit()) { long firstHam = hamSet.isEmpty() ? Long.MAX_VALUE : hamSet.first(); long firstSpam = spamSet.isEmpty() ? Long.MAX_VALUE : spamSet.first(); long time = Math.min(firstHam, firstSpam); boolean hamChanged = hamSet.remove(time); boolean spamChanged = spamSet.remove(time); CacheDistribution.CHANGED |= hamChanged || spamChanged; } } public synchronized boolean addSpam(long time) { boolean hamChanged = hamSet.remove(time); boolean spamChanged = spamSet.add(time); boolean changed = hamChanged || spamChanged; CacheDistribution.CHANGED |= changed; hairCut(); return changed; } public boolean isSpam(long time) { return spamSet.contains(time); } public synchronized boolean addHam(long time) { boolean hamChanged = hamSet.add(time); boolean spamChanged = spamSet.remove(time); boolean changed = hamChanged || spamChanged; CacheDistribution.CHANGED |= changed; hairCut(); return changed; } public int[] getBinomial() { if (frequency == null) { return new int[2]; } else if (ready) { int[] result = new int[2]; result[0] = hamSet.size(); result[1] = spamSet.size(); return result; } else if (frequency.getMinimum() > 0.0d) { int complain = spamSet.size(); int[] result = new int[2]; double semana = 60 * 60 * 24 * 7; int total = (int) (semana / frequency.getMinimum()); if (total < complain) { total = complain; } int ham = total - complain; int spam = complain; result[0] = ham; result[1] = spam; return result; } else { return new int[2]; } } public int[] getBinomial(String token) { int[] result = new int[2]; if (token != null) { for (Peer peer : Peer.getSet()) { short reputationMax = peer.getReputationMax(); if (reputationMax > 0) { Peer.Binomial peerBinomial = peer.getReputation(token); if (peerBinomial != null) { int hamInt = peerBinomial.getHAM(); int spamInt = peerBinomial.getSPAM(); int total = hamInt + spamInt; if (total > reputationMax) { float proporcion = (float) reputationMax / total; hamInt = (int) (hamInt * proporcion); spamInt = (int) (spamInt * proporcion); } result[0] += hamInt; result[1] += spamInt; } } } } if (ready) { result[0] += hamSet.size(); result[1] += spamSet.size(); return result; } else if (frequency != null && frequency.getMinimum() > 0.0d) { int complain = spamSet.size(); double semana = 60 * 60 * 24 * 7; int total = (int) (semana / frequency.getMinimum()); if (total < complain) { total = complain; } int ham = total - complain; int spam = complain; result[0] += ham; result[1] += spam; } return result; } @Override public String toString() { return Float.toString(getSpamProbability(null)); } } /** * Classe que representa a distribuio binomial entre HAM e SPAM. */ public static final class Binomial implements Serializable { private static final long serialVersionUID = 1L; private int ham; // Quantidade total de HAM em sete dias. private int spam; // Quantidade total de SPAM em sete dias private final Status status; private NormalDistribution frequency = null; private long lastQuery = 0; public Binomial(String token, Distribution distribution) { int[] binomial = distribution.getBinomial(token); this.ham = binomial[0]; this.spam = binomial[1]; this.status = distribution.getStatus(token); this.frequency = distribution.frequency; this.lastQuery = distribution.lastQuery; } public Binomial(Status status) { this.status = status; this.ham = 0; this.spam = 0; this.frequency = null; this.lastQuery = 0; } public void add(String token, Distribution distribution) { int[] binomial = distribution.getBinomial(token); this.ham += binomial[0]; this.spam += binomial[1]; if (this.frequency == null) { this.frequency = distribution.frequency; } else { this.frequency.add(distribution.frequency); } if (this.lastQuery < distribution.lastQuery) { this.lastQuery = distribution.lastQuery; } } public int getSPAM() { return spam; } public int getHAM() { return ham; } public Status getStatus() { return status; } public boolean clear() { if (spam > 0) { this.ham += spam; this.spam = 0; return true; } else { return false; } } public float getSpamProbability() { int total = ham + spam; float probability = (float) spam / (float) total; if (total == 0) { return 0.0f; } else if (probability > LIMIAR1 && spam < 5) { // Quantidade pequena para dar preciso. return LIMIAR1; } else if (probability > LIMIAR2 && spam < 7) { // Quantidade pequena para dar preciso. // Para haver bloqueio temporrio // necessrio pelo meno cinco denuncias. return LIMIAR2; } else if (probability > LIMIAR3 && spam < 13) { // Quantidade pequena para dar preciso. // Para haver bloqueio definitivo // necessrio pelo meno sete denuncias. return LIMIAR3; } else { return probability; } } public long getIdleTimeMillis() { if (lastQuery == 0) { return 0; } else { return System.currentTimeMillis() - lastQuery; } } public String getFrequencyLiteral() { if (frequency == null) { return "UNDEFINED"; } else { int frequencyInt = frequency.getMaximumInt(); long idleTimeInt = getIdleTimeMillis(); if (idleTimeInt > frequencyInt * 5 && idleTimeInt > 604800000) { return "DEAD"; } else { char sinal = '~'; if (idleTimeInt > frequencyInt * 3) { sinal = '>'; } if (frequencyInt >= 3600000) { return sinal + ((frequencyInt / 3600000) + "h"); } else if (frequencyInt >= 60000) { return sinal + ((frequencyInt / 60000) + "min"); } else if (frequencyInt >= 1000) { return sinal + ((frequencyInt / 1000) + "s"); } else { return sinal + (frequencyInt + "ms"); } } } } @Override public String toString() { return Float.toString(getSpamProbability()); } } }