1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20
21 package org.apache.james.jspf.terms;
22
23 import org.apache.james.jspf.core.DNSLookupContinuation;
24 import org.apache.james.jspf.core.DNSRequest;
25 import org.apache.james.jspf.core.DNSResponse;
26 import org.apache.james.jspf.core.MacroExpand;
27 import org.apache.james.jspf.core.MacroExpandEnabled;
28 import org.apache.james.jspf.core.SPF1Constants;
29 import org.apache.james.jspf.core.SPFChecker;
30 import org.apache.james.jspf.core.SPFCheckerDNSResponseListener;
31 import org.apache.james.jspf.core.SPFSession;
32 import org.apache.james.jspf.core.SPFTermsRegexps;
33 import org.apache.james.jspf.core.exceptions.NeutralException;
34 import org.apache.james.jspf.core.exceptions.NoneException;
35 import org.apache.james.jspf.core.exceptions.PermErrorException;
36 import org.apache.james.jspf.core.exceptions.TempErrorException;
37 import org.apache.james.jspf.core.exceptions.TimeoutException;
38
39 import java.util.List;
40
41 /**
42 * This class represent the exp modifier
43 *
44 */
45 public class ExpModifier extends GenericModifier implements MacroExpandEnabled, SPFCheckerDNSResponseListener {
46
47 private final class ExpandedExplanationChecker implements SPFChecker {
48
49 /**
50 * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
51 */
52 public DNSLookupContinuation checkSPF(SPFSession spfData)
53 throws PermErrorException, NoneException,
54 TempErrorException, NeutralException {
55 try {
56 String exp = (String) spfData.getAttribute(ATTRIBUTE_EXPAND_EXPLANATION);
57 String expandedExplanation = macroExpand.expand(exp, spfData, MacroExpand.EXPLANATION);
58 spfData.setExplanation(expandedExplanation);
59 } catch (PermErrorException e) {
60 // ignore syntax error on explanation expansion
61 }
62 return null;
63 }
64 }
65
66
67 private final class ExpandedChecker implements SPFChecker {
68
69 /**
70 * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
71 */
72 public DNSLookupContinuation checkSPF(SPFSession spfData) throws PermErrorException,
73 NoneException, TempErrorException, NeutralException {
74 String host = macroExpand.expand(getHost(), spfData, MacroExpand.DOMAIN);
75
76 return new DNSLookupContinuation(new DNSRequest(host, DNSRequest.TXT), ExpModifier.this);
77 }
78 }
79
80
81 private static final String ATTRIBUTE_EXPAND_EXPLANATION = "ExpModifier.ExpandExplanation";
82
83 /**
84 * ABNF: explanation = "exp" "=" domain-spec
85 *
86 * NOTE: the last +"?" has been added to support RFC4408 ERRATA for the EXP modifier.
87 * An "exp=" should not result in a perm error but should be ignored.
88 * Errata: http://www.openspf.org/RFC_4408/Errata#empty-exp
89 *
90 * NOTE: the last +"?" has been then removed because OpenSPF released a new testsuite
91 * requiring a PermError on "exp=" (see JSPF-56).
92 */
93 public static final String REGEX = "[eE][xX][pP]" + "\\="
94 + SPFTermsRegexps.DOMAIN_SPEC_REGEX;
95
96 private MacroExpand macroExpand;
97
98 private ExpandedChecker expandedChecker = new ExpandedChecker();
99
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 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 = (String) 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 }