001 /**************************************************************** 002 * Licensed to the Apache Software Foundation (ASF) under one * 003 * or more contributor license agreements. See the NOTICE file * 004 * distributed with this work for additional information * 005 * regarding copyright ownership. The ASF licenses this file * 006 * to you under the Apache License, Version 2.0 (the * 007 * "License"); you may not use this file except in compliance * 008 * with the License. You may obtain a copy of the License at * 009 * * 010 * http://www.apache.org/licenses/LICENSE-2.0 * 011 * * 012 * Unless required by applicable law or agreed to in writing, * 013 * software distributed under the License is distributed on an * 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * 015 * KIND, either express or implied. See the License for the * 016 * specific language governing permissions and limitations * 017 * under the License. * 018 ****************************************************************/ 019 020 021 package org.apache.james.jspf.terms; 022 023 import org.apache.james.jspf.core.DNSLookupContinuation; 024 import org.apache.james.jspf.core.DNSRequest; 025 import org.apache.james.jspf.core.DNSResponse; 026 import org.apache.james.jspf.core.MacroExpand; 027 import org.apache.james.jspf.core.MacroExpandEnabled; 028 import org.apache.james.jspf.core.SPF1Constants; 029 import org.apache.james.jspf.core.SPFChecker; 030 import org.apache.james.jspf.core.SPFCheckerDNSResponseListener; 031 import org.apache.james.jspf.core.SPFSession; 032 import org.apache.james.jspf.core.SPFTermsRegexps; 033 import org.apache.james.jspf.core.exceptions.NeutralException; 034 import org.apache.james.jspf.core.exceptions.NoneException; 035 import org.apache.james.jspf.core.exceptions.PermErrorException; 036 import org.apache.james.jspf.core.exceptions.TempErrorException; 037 import org.apache.james.jspf.core.exceptions.TimeoutException; 038 039 import java.util.List; 040 041 /** 042 * This class represent the exp modifier 043 * 044 */ 045 public class ExpModifier extends GenericModifier implements MacroExpandEnabled, SPFCheckerDNSResponseListener { 046 047 private final class ExpandedExplanationChecker implements SPFChecker { 048 049 /** 050 * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession) 051 */ 052 public DNSLookupContinuation checkSPF(SPFSession spfData) 053 throws PermErrorException, NoneException, 054 TempErrorException, NeutralException { 055 try { 056 String exp = (String) spfData.getAttribute(ATTRIBUTE_EXPAND_EXPLANATION); 057 String expandedExplanation = macroExpand.expand(exp, spfData, MacroExpand.EXPLANATION); 058 spfData.setExplanation(expandedExplanation); 059 } catch (PermErrorException e) { 060 // ignore syntax error on explanation expansion 061 } 062 return null; 063 } 064 } 065 066 067 private final class ExpandedChecker implements SPFChecker { 068 069 /** 070 * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession) 071 */ 072 public DNSLookupContinuation checkSPF(SPFSession spfData) throws PermErrorException, 073 NoneException, TempErrorException, NeutralException { 074 String host = macroExpand.expand(getHost(), spfData, MacroExpand.DOMAIN); 075 076 return new DNSLookupContinuation(new DNSRequest(host, DNSRequest.TXT), ExpModifier.this); 077 } 078 } 079 080 081 private static final String ATTRIBUTE_EXPAND_EXPLANATION = "ExpModifier.ExpandExplanation"; 082 083 /** 084 * ABNF: explanation = "exp" "=" domain-spec 085 * 086 * NOTE: the last +"?" has been added to support RFC4408 ERRATA for the EXP modifier. 087 * An "exp=" should not result in a perm error but should be ignored. 088 * Errata: http://www.openspf.org/RFC_4408/Errata#empty-exp 089 * 090 * NOTE: the last +"?" has been then removed because OpenSPF released a new testsuite 091 * requiring a PermError on "exp=" (see JSPF-56). 092 */ 093 public static final String REGEX = "[eE][xX][pP]" + "\\=" 094 + SPFTermsRegexps.DOMAIN_SPEC_REGEX; 095 096 private MacroExpand macroExpand; 097 098 private ExpandedChecker expandedChecker = new ExpandedChecker(); 099 100 private ExpandedExplanationChecker expandedExplanationChecker = new ExpandedExplanationChecker(); 101 102 /** 103 * Generate the explanation and set it in SPF1Data so it can be accessed 104 * easy later if needed 105 * 106 * @param spfData 107 * The SPF1Data which should used 108 * @throws PermErrorException 109 * @throws TempErrorException 110 * @throws NoneException 111 * @throws NeutralException 112 */ 113 protected DNSLookupContinuation checkSPFLogged(SPFSession spfData) throws PermErrorException, TempErrorException, NeutralException, NoneException { 114 String host = getHost(); 115 116 // RFC4408 Errata: http://www.openspf.org/RFC_4408/Errata#empty-exp 117 if (host == null) { 118 return null; 119 } 120 121 // If we should ignore the explanation we don't have to run this class 122 if (spfData.ignoreExplanation() == true) 123 return null; 124 125 // If the currentResult is not fail we have no need to run all these 126 // methods! 127 if (spfData.getCurrentResult()== null || !spfData.getCurrentResult().equals(SPF1Constants.FAIL)) 128 return null; 129 130 spfData.pushChecker(expandedChecker); 131 return macroExpand.checkExpand(host, spfData, MacroExpand.DOMAIN); 132 } 133 134 /** 135 * Get TXT records as a string 136 * 137 * @param dns The DNSService to query 138 * @param strServer 139 * The hostname for which we want to retrieve the TXT-Record 140 * @return String which reflect the TXT-Record 141 * @throws PermErrorException 142 * if more then one TXT-Record for explanation was found 143 * @throws NoneException 144 * @throws NeutralException 145 * @throws TempErrorException 146 * @throws TempErrorException 147 * if the lookup result was "TRY_AGAIN" 148 */ 149 150 /** 151 * @see org.apache.james.jspf.core.SPFCheckerDNSResponseListener#onDNSResponse(org.apache.james.jspf.core.DNSResponse, org.apache.james.jspf.core.SPFSession) 152 */ 153 public DNSLookupContinuation onDNSResponse(DNSResponse lookup, SPFSession spfData) throws PermErrorException, TempErrorException, NeutralException, NoneException { 154 try { 155 List<String> records = lookup.getResponse(); 156 157 if (records == null) { 158 return null; 159 } 160 161 // See SPF-Spec 6.2 162 // 163 // If domain-spec is empty, or there are any DNS processing errors (any RCODE other than 0), 164 // or if no records are returned, or if more than one record is returned, or if there are syntax 165 // errors in the explanation string, then proceed as if no exp modifier was given. 166 if (records.size() > 1) { 167 168 log.debug("More then one TXT-Record found for explanation"); 169 // Only catch the error and return null 170 171 } else { 172 173 String exp = records.get(0); 174 if (exp.length()>=2 && exp.charAt(0) == '"' && exp.charAt(exp.length() -1 ) == '"') { 175 exp = exp.substring(1, exp.length() - 1); 176 } 177 178 spfData.setAttribute(ATTRIBUTE_EXPAND_EXPLANATION, exp); 179 180 if ((exp != null) && (!exp.equals(""))) { 181 182 try { 183 spfData.pushChecker(expandedExplanationChecker); 184 return macroExpand.checkExpand(exp, spfData, MacroExpand.EXPLANATION); 185 } catch (PermErrorException e) { 186 // ignore syntax error on explanation expansion 187 } 188 } 189 190 } 191 192 193 } catch (TimeoutException e) { 194 // Nothing todo here.. just return null 195 } 196 return null; 197 } 198 199 /** 200 * @see java.lang.Object#toString() 201 */ 202 public String toString() { 203 return "exp="+getHost(); 204 } 205 206 /** 207 * @see org.apache.james.jspf.core.MacroExpandEnabled#enableMacroExpand(org.apache.james.jspf.core.MacroExpand) 208 */ 209 public void enableMacroExpand(MacroExpand macroExpand) { 210 this.macroExpand = macroExpand; 211 } 212 213 }