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.impl;
22  
23  import org.apache.james.jspf.core.DNSLookupContinuation;
24  import org.apache.james.jspf.core.DNSService;
25  import org.apache.james.jspf.core.DNSServiceEnabled;
26  import org.apache.james.jspf.core.LogEnabled;
27  import org.apache.james.jspf.core.Logger;
28  import org.apache.james.jspf.core.MacroExpand;
29  import org.apache.james.jspf.core.MacroExpandEnabled;
30  import org.apache.james.jspf.core.SPF1Record;
31  import org.apache.james.jspf.core.SPF1Utils;
32  import org.apache.james.jspf.core.SPFCheckEnabled;
33  import org.apache.james.jspf.core.SPFChecker;
34  import org.apache.james.jspf.core.SPFCheckerExceptionCatcher;
35  import org.apache.james.jspf.core.SPFRecordParser;
36  import org.apache.james.jspf.core.SPFSession;
37  import org.apache.james.jspf.core.exceptions.NeutralException;
38  import org.apache.james.jspf.core.exceptions.NoneException;
39  import org.apache.james.jspf.core.exceptions.PermErrorException;
40  import org.apache.james.jspf.core.exceptions.SPFErrorConstants;
41  import org.apache.james.jspf.core.exceptions.SPFResultException;
42  import org.apache.james.jspf.core.exceptions.TempErrorException;
43  import org.apache.james.jspf.executor.FutureSPFResult;
44  import org.apache.james.jspf.executor.SPFExecutor;
45  import org.apache.james.jspf.executor.SPFResult;
46  import org.apache.james.jspf.executor.SynchronousSPFExecutor;
47  import org.apache.james.jspf.parser.RFC4408SPF1Parser;
48  import org.apache.james.jspf.policies.InitialChecksPolicy;
49  import org.apache.james.jspf.policies.NeutralIfNotMatchPolicy;
50  import org.apache.james.jspf.policies.NoSPFRecordFoundPolicy;
51  import org.apache.james.jspf.policies.ParseRecordPolicy;
52  import org.apache.james.jspf.policies.Policy;
53  import org.apache.james.jspf.policies.PolicyPostFilter;
54  import org.apache.james.jspf.policies.SPFRetriever;
55  import org.apache.james.jspf.policies.SPFStrictCheckerRetriever;
56  import org.apache.james.jspf.policies.local.BestGuessPolicy;
57  import org.apache.james.jspf.policies.local.DefaultExplanationPolicy;
58  import org.apache.james.jspf.policies.local.FallbackPolicy;
59  import org.apache.james.jspf.policies.local.OverridePolicy;
60  import org.apache.james.jspf.policies.local.TrustedForwarderPolicy;
61  import org.apache.james.jspf.wiring.WiringServiceTable;
62  
63  import java.util.Iterator;
64  import java.util.LinkedList;
65  
66  /**
67   * This class is used to generate a SPF-Test and provided all intressting data.
68   */
69  public class SPF implements SPFChecker {
70  
71      private static final class SPFRecordChecker implements SPFChecker {
72          
73          /**
74           * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
75           */
76          public DNSLookupContinuation checkSPF(SPFSession spfData)
77                  throws PermErrorException, TempErrorException,
78                  NeutralException, NoneException {
79              
80              SPF1Record spfRecord = (SPF1Record) spfData.getAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD);
81              // make sure we cleanup the record, for recursion support
82              spfData.removeAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD);
83              
84              LinkedList policyCheckers = new LinkedList();
85              
86              Iterator i = spfRecord.iterator();
87              while (i.hasNext()) {
88                  SPFChecker checker = (SPFChecker) i.next();
89                  policyCheckers.add(checker);
90              }
91  
92              while (policyCheckers.size() > 0) {
93                  SPFChecker removeLast = (SPFChecker) policyCheckers.removeLast();
94                  spfData.pushChecker(removeLast);
95              }
96  
97              return null;
98          }
99      }
100 
101     private static final class PolicyChecker implements SPFChecker {
102         
103         private LinkedList policies;
104         
105         public PolicyChecker(LinkedList policies) {
106             this.policies = policies;
107         }
108         
109         /**
110          * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
111          */
112         public DNSLookupContinuation checkSPF(SPFSession spfData)
113                 throws PermErrorException, TempErrorException,
114                 NeutralException, NoneException {
115             
116             while (policies.size() > 0) {
117                 SPFChecker removeLast = (SPFChecker) policies.removeLast();
118                 spfData.pushChecker(removeLast);
119             }
120             
121             return null;
122         }
123     }
124 
125     private static final class SPFPolicyChecker implements SPFChecker {
126         private Policy policy;
127 
128         /**
129          * @param policy
130          */
131         public SPFPolicyChecker(Policy policy) {
132             this.policy = policy;
133         }
134 
135         /**
136          * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
137          */
138         public DNSLookupContinuation checkSPF(SPFSession spfData)
139                 throws PermErrorException, TempErrorException,
140                 NeutralException, NoneException {
141             SPF1Record res = (SPF1Record) spfData.getAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD);
142             if (res == null) {
143                 res = policy.getSPFRecord(spfData.getCurrentDomain());
144                 spfData.setAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD, res);
145             }
146             return null;
147         }
148         
149         public String toString() {
150             return "PC:"+policy.toString();
151         }
152     }
153 
154     private static final class SPFPolicyPostFilterChecker implements SPFChecker {
155         private PolicyPostFilter policy;
156 
157         /**
158          * @param policy
159          */
160         public SPFPolicyPostFilterChecker(PolicyPostFilter policy) {
161             this.policy = policy;
162         }
163 
164         /**
165          * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
166          */
167         public DNSLookupContinuation checkSPF(SPFSession spfData)
168                 throws PermErrorException, TempErrorException,
169                 NeutralException, NoneException {
170             SPF1Record res = (SPF1Record) spfData.getAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD);
171             res = policy.getSPFRecord(spfData.getCurrentDomain(), res);
172             spfData.setAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD, res);
173             return null;
174         }
175         
176         public String toString() {
177             return "PFC:"+policy.toString();
178         }
179 
180     }
181 
182     private DNSService dnsProbe;
183 
184     private SPFRecordParser parser;
185 
186     private Logger log;
187     
188     private String defaultExplanation = null;
189     
190     private boolean useBestGuess = false;
191 
192     private FallbackPolicy fallBack;
193     
194     private OverridePolicy override;
195     
196     private boolean useTrustedForwarder = false;
197     
198     private boolean mustEquals = false;
199 
200     private MacroExpand macroExpand;
201 
202     private SPFExecutor executor;
203 
204     /**
205      * Uses passed logger and passed dnsServicer
206      * 
207      * @param dnsProbe the dns provider
208      * @param logger the logger to use
209      */
210     public SPF(DNSService dnsProbe, Logger logger) {
211         super();
212         this.dnsProbe = dnsProbe;
213         this.log = logger;
214         WiringServiceTable wiringService = new WiringServiceTable();
215         wiringService.put(LogEnabled.class, this.log);
216         wiringService.put(DNSServiceEnabled.class, this.dnsProbe);
217         this.macroExpand = new MacroExpand(logger.getChildLogger("macroExpand"), this.dnsProbe);
218         wiringService.put(MacroExpandEnabled.class, this.macroExpand);
219         this.parser = new RFC4408SPF1Parser(logger.getChildLogger("parser"), new DefaultTermsFactory(logger.getChildLogger("termsfactory"), wiringService));
220         // We add this after the parser creation because services cannot be null
221         wiringService.put(SPFCheckEnabled.class, this);
222         this.executor = new SynchronousSPFExecutor(log, dnsProbe);
223     }
224     
225     
226     /**
227      * Uses passed services
228      * 
229      * @param dnsProbe the dns provider
230      * @param parser the parser to use
231      * @param logger the logger to use
232      */
233     public SPF(DNSService dnsProbe, SPFRecordParser parser, Logger logger, MacroExpand macroExpand, SPFExecutor executor) {
234         super();
235         this.dnsProbe = dnsProbe;
236         this.parser = parser;
237         this.log = logger;
238         this.macroExpand = macroExpand;
239         this.executor = executor;
240     }
241 
242     
243     private static final class DefaultSPFChecker implements SPFChecker, SPFCheckerExceptionCatcher {
244         
245         private Logger log;
246 
247         public DefaultSPFChecker(Logger log) {
248             this.log = log;
249         }
250 
251         /**
252          * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
253          */
254         public DNSLookupContinuation checkSPF(SPFSession spfData)
255                 throws PermErrorException, TempErrorException,
256                 NeutralException, NoneException {
257             if (spfData.getCurrentResultExpanded() == null) {
258                 String resultChar = spfData.getCurrentResult() != null ? spfData.getCurrentResult() : "";
259                 String result = SPF1Utils.resultToName(resultChar);
260                 spfData.setCurrentResultExpanded(result);
261             }
262             return null;
263         }
264         
265 
266         /**
267          * @see org.apache.james.jspf.core.SPFCheckerExceptionCatcher#onException(java.lang.Exception, org.apache.james.jspf.core.SPFSession)
268          */
269         public void onException(Exception exception, SPFSession session)
270                 throws PermErrorException, NoneException, TempErrorException,
271                 NeutralException {
272 
273             String result;
274             if (exception instanceof SPFResultException) {
275                 result = ((SPFResultException) exception).getResult();
276                 if (!SPFErrorConstants.NEUTRAL_CONV.equals(result)) {
277                     log.warn(exception.getMessage(),exception);
278                 }
279             } else {
280                 // this should never happen at all. But anyway we will set the
281                 // result to neutral. Safety first ..
282                 log.error(exception.getMessage(),exception);
283                 result = SPFErrorConstants.NEUTRAL_CONV;
284             }
285             session.setCurrentResultExpanded(result);
286         }
287 
288     }
289     
290     /**
291      * Run check for SPF with the given values.
292      * 
293      * @param ipAddress
294      *            The ipAddress the connection is comming from
295      * @param mailFrom
296      *            The mailFrom which was provided
297      * @param hostName
298      *            The hostname which was provided as HELO/EHLO
299      * @return result The SPFResult
300      */
301     public SPFResult checkSPF(String ipAddress, String mailFrom, String hostName) {
302         SPFSession spfData = null;
303 
304         // Setup the data
305         spfData = new SPFSession(mailFrom, hostName, ipAddress);
306       
307 
308         SPFChecker resultHandler = new DefaultSPFChecker(log);
309         
310         spfData.pushChecker(resultHandler);
311         spfData.pushChecker(this);
312         
313         FutureSPFResult ret = new FutureSPFResult();
314         
315         executor.execute(spfData, ret);
316 
317         // if we call ret.getResult it waits the result ;-)
318 //        log.info("[ipAddress=" + ipAddress + "] [mailFrom=" + mailFrom
319 //                + "] [helo=" + hostName + "] => " + ret.getResult());
320 
321         return ret;
322 
323     }
324 
325 
326     /**
327      * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
328      */
329     public DNSLookupContinuation checkSPF(SPFSession spfData) throws PermErrorException,
330             NoneException, TempErrorException, NeutralException {
331 
332         // if we already have a result we don't need to add further processing.
333         if (spfData.getCurrentResultExpanded() == null && spfData.getCurrentResult() == null) {
334             SPFChecker policyChecker = new PolicyChecker(getPolicies());
335             SPFChecker recordChecker = new SPFRecordChecker();
336             
337             spfData.pushChecker(recordChecker);
338             spfData.pushChecker(policyChecker);
339         }
340         
341         return null;
342     }
343 
344     /**
345      * Return a default policy for SPF
346      */
347     public LinkedList getPolicies() {
348 
349         LinkedList policies = new LinkedList();
350         
351         if (override != null) {
352             policies.add(new SPFPolicyChecker(override));
353         }
354 
355         policies.add(new InitialChecksPolicy());
356 
357         if (mustEquals) {
358             policies.add(new SPFStrictCheckerRetriever());
359         } else {
360             policies.add(new SPFRetriever());
361         }
362 
363         if (useBestGuess) {
364             policies.add(new SPFPolicyPostFilterChecker(new BestGuessPolicy()));
365         }
366         
367         policies.add(new SPFPolicyPostFilterChecker(new ParseRecordPolicy(parser)));
368         
369         if (fallBack != null) {
370             policies.add(new SPFPolicyPostFilterChecker(fallBack));
371         }
372 
373         policies.add(new SPFPolicyPostFilterChecker(new NoSPFRecordFoundPolicy()));
374         
375         // trustedForwarder support is enabled
376         if (useTrustedForwarder) {
377             policies.add(new SPFPolicyPostFilterChecker(new TrustedForwarderPolicy(log)));
378         }
379 
380         policies.add(new SPFPolicyPostFilterChecker(new NeutralIfNotMatchPolicy()));
381 
382         policies.add(new SPFPolicyPostFilterChecker(new DefaultExplanationPolicy(log, defaultExplanation, macroExpand)));
383         
384         return policies;
385     }
386     
387     /**
388      * Set the amount of time (in seconds) before an TermError is returned when
389      * the dnsserver not answer. Default is 20 seconds.
390      * 
391      * @param timeOut The timout in seconds
392      */
393     public synchronized void setTimeOut(int timeOut) {
394         log.debug("TimeOut was set to: " + timeOut);
395         dnsProbe.setTimeOut(timeOut);
396     }
397     
398     /**
399      * Set the default explanation which will be used if no explanation is found in the SPF Record
400      *  
401      * @param defaultExplanation The explanation to use if no explanation is found in the SPF Record
402      */
403     public synchronized void setDefaultExplanation(String defaultExplanation) {
404         this.defaultExplanation = defaultExplanation;      
405     }
406     
407     /**
408      * Set to true for using best guess. Best guess will set the SPF-Record to "a/24 mx/24 ptr ~all" 
409      * if no SPF-Record was found for the doamin. When this was happen only pass or netural will be returned.
410      * Default is false.
411      * 
412      * @param useBestGuess true to enable best guess
413      */
414     public synchronized void setUseBestGuess(boolean useBestGuess) {
415         this.useBestGuess  = useBestGuess;
416     }
417     
418     
419     /**
420      * Return the FallbackPolicy object which can be used to 
421      * provide default spfRecords for hosts which have no records
422      * 
423      * @return the FallbackPolicy object
424      */
425     public synchronized FallbackPolicy getFallbackPolicy() {
426         // Initialize fallback policy
427         if (fallBack == null) {
428             this.fallBack =  new FallbackPolicy(log.getChildLogger("fallbackpolicy"), parser);
429         }
430         return fallBack;
431     }
432     
433     /**
434      * Set to true to enable trusted-forwarder.org whitelist. The whitelist will only be queried if
435      * the last Mechanism is -all or ?all. 
436      * See http://trusted-forwarder.org for more informations
437      * Default is false.
438      * 
439      * @param useTrustedForwarder true or false
440      */
441     public synchronized void setUseTrustedForwarder(boolean useTrustedForwarder) {
442         this.useTrustedForwarder = useTrustedForwarder;
443     }
444     
445     /**
446      * Return the OverridePolicy object which can be used to
447      * override spfRecords for hosts
448      * 
449      * @return the OverridePolicy object
450      */
451     public synchronized OverridePolicy getOverridePolicy() {
452         if (override == null) {
453             override = new OverridePolicy(log.getChildLogger("overridepolicy"), parser);
454         }
455         return override;
456     }
457     
458     /**
459      * Set to true if a PermError should returned when a domain publish a SPF-Type
460      * and TXT-Type SPF-Record and both are not equals. Defaults false
461      * 
462      * @param mustEquals true or false
463      */
464     public synchronized void setSPFMustEqualsTXT(boolean mustEquals) {
465         this.mustEquals = mustEquals;
466     }
467 
468 
469 }