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 }