View Javadoc

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 }