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    package org.apache.james.jspf.policies;
021    
022    import org.apache.james.jspf.core.DNSLookupContinuation;
023    import org.apache.james.jspf.core.DNSRequest;
024    import org.apache.james.jspf.core.DNSResponse;
025    import org.apache.james.jspf.core.SPF1Constants;
026    import org.apache.james.jspf.core.SPF1Record;
027    import org.apache.james.jspf.core.SPF1Utils;
028    import org.apache.james.jspf.core.SPFChecker;
029    import org.apache.james.jspf.core.SPFCheckerDNSResponseListener;
030    import org.apache.james.jspf.core.SPFSession;
031    import org.apache.james.jspf.core.exceptions.NeutralException;
032    import org.apache.james.jspf.core.exceptions.NoneException;
033    import org.apache.james.jspf.core.exceptions.PermErrorException;
034    import org.apache.james.jspf.core.exceptions.TempErrorException;
035    import org.apache.james.jspf.core.exceptions.TimeoutException;
036    
037    import java.util.Iterator;
038    import java.util.List;
039    
040    /**
041     * Get the raw dns txt or spf entry which contains a spf entry
042     */
043    public class SPFRetriever implements SPFChecker {
044        
045        private static final class SPFRecordHandlerDNSResponseListener implements SPFCheckerDNSResponseListener {
046    
047            /**
048             * @see org.apache.james.jspf.core.SPFCheckerDNSResponseListener#onDNSResponse(org.apache.james.jspf.core.DNSResponse, org.apache.james.jspf.core.SPFSession)
049             */
050            public DNSLookupContinuation onDNSResponse(
051                    DNSResponse response, SPFSession session)
052                    throws PermErrorException,
053                    NoneException, TempErrorException,
054                    NeutralException {
055                
056                List<String> spfR;
057                try {
058                    spfR = response.getResponse();
059                    String record = extractSPFRecord(spfR);
060                    if (record != null) {
061                        session.setAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD, new SPF1Record(record));
062                    }
063                } catch (TimeoutException e) {
064                    throw new TempErrorException("Timeout querying dns");
065                }
066                return null;
067                
068            }
069            
070        }
071    
072        private static final class SPFRetrieverDNSResponseListener implements SPFCheckerDNSResponseListener {
073    
074            /**
075             * @see org.apache.james.jspf.core.SPFCheckerDNSResponseListener#onDNSResponse(org.apache.james.jspf.core.DNSResponse, org.apache.james.jspf.core.SPFSession)
076             */
077            public DNSLookupContinuation onDNSResponse(
078                    DNSResponse response, SPFSession session)
079                    throws PermErrorException, NoneException,
080                    TempErrorException, NeutralException {
081                try {
082                    List<String> spfR = response.getResponse();
083                    
084                    if (spfR == null || spfR.isEmpty()) {
085                        
086                        String currentDomain = session.getCurrentDomain();
087                        return new DNSLookupContinuation(new DNSRequest(currentDomain, DNSRequest.TXT), new SPFRecordHandlerDNSResponseListener());
088    
089                    } else {
090                        
091                        String record = extractSPFRecord(spfR);
092                        if (record != null) {
093                            session.setAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD, new SPF1Record(record));
094                        }
095                        
096                    }
097                    
098                    return null;
099                    
100                } catch (TimeoutException e) {
101                    throw new TempErrorException("Timeout querying dns");
102                }
103            }
104            
105        }
106    
107            /**
108             * This is used for testing purpose. Setting this to true will skip the initial
109             * lookups for SPF records and instead will simply check the TXT records.
110             */
111            private static final boolean CHECK_ONLY_TXT_RECORDS = false;
112        
113        /**
114         * Return the extracted SPF-Record 
115         *  
116         * @param spfR the List which holds TXT/SPF - Records
117         * @return returnValue the extracted SPF-Record
118         * @throws PermErrorException if more then one SPF - Record was found in the 
119         *                            given List.
120         */
121        protected static String extractSPFRecord(List<String> spfR) throws PermErrorException {
122            if (spfR == null || spfR.isEmpty()) return null;
123            
124            String returnValue = null;
125            Iterator<String> all = spfR.iterator();
126               
127            while (all.hasNext()) {
128                // DO NOT trim the result!
129                String compare = all.next();
130    
131                // We trim the compare value only for the comparison
132                if (compare.toLowerCase().trim().startsWith(SPF1Constants.SPF_VERSION1 + " ") || compare.trim().equalsIgnoreCase(SPF1Constants.SPF_VERSION1)) {
133                    if (returnValue == null) {
134                        returnValue = compare;
135                    } else {
136                        throw new PermErrorException(
137                                "More than 1 SPF record found");
138                    }
139                }
140            }
141            
142            return returnValue;
143        }
144        
145    
146        /**
147         * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
148         */
149        public DNSLookupContinuation checkSPF(SPFSession spfData)
150                throws PermErrorException, TempErrorException, NeutralException,
151                NoneException {
152            SPF1Record res = (SPF1Record) spfData.getAttribute(SPF1Utils.ATTRIBUTE_SPF1_RECORD);
153            if (res == null) {
154                String currentDomain = spfData.getCurrentDomain();
155                if (CHECK_ONLY_TXT_RECORDS) {
156                    return new DNSLookupContinuation(new DNSRequest(currentDomain, DNSRequest.TXT), new SPFRecordHandlerDNSResponseListener());
157                } else {
158                    return new DNSLookupContinuation(new DNSRequest(currentDomain, DNSRequest.SPF), new SPFRetrieverDNSResponseListener());
159                }
160                
161            }
162            return null;
163        }
164    
165    }