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.IPAddr;
027    import org.apache.james.jspf.core.MacroExpand;
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.SPFTermsRegexps;
032    import org.apache.james.jspf.core.exceptions.NeutralException;
033    import org.apache.james.jspf.core.exceptions.NoneException;
034    import org.apache.james.jspf.core.exceptions.PermErrorException;
035    import org.apache.james.jspf.core.exceptions.TempErrorException;
036    import org.apache.james.jspf.core.exceptions.TimeoutException;
037    
038    import java.util.ArrayList;
039    import java.util.List;
040    
041    /**
042     * This class represent the mx mechanism
043     * 
044     */
045    public class MXMechanism extends AMechanism implements SPFCheckerDNSResponseListener {
046    
047        private final class ExpandedChecker 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) throws PermErrorException,
053                    TempErrorException, NeutralException, NoneException {
054    
055                // Get the right host.
056                String host = expandHost(spfData);
057                
058                return new DNSLookupContinuation(new DNSRequest(host, DNSRequest.MX), MXMechanism.this);
059            }
060        }
061    
062        private static final String ATTRIBUTE_MX_RECORDS = "MXMechanism.mxRecords";
063        private static final String ATTRIBUTE_CHECK_RECORDS = "MXMechanism.checkRecords";
064        /**
065         * ABNF: MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ]
066         */
067        public static final String REGEX = "[mM][xX]" + "(?:\\:"
068                + SPFTermsRegexps.DOMAIN_SPEC_REGEX + ")?" + "(?:"
069                + DUAL_CIDR_LENGTH_REGEX + ")?";
070        
071        private SPFChecker expandedChecker = new ExpandedChecker();
072        
073        /**
074         * @see org.apache.james.jspf.terms.AMechanism#checkSPF(org.apache.james.jspf.core.SPFSession)
075         */
076        public DNSLookupContinuation checkSPF(SPFSession spfData) throws PermErrorException,
077                TempErrorException, NeutralException, NoneException{
078    
079            // update currentDepth
080            spfData.increaseCurrentDepth();
081    
082            spfData.pushChecker(expandedChecker);
083            return macroExpand.checkExpand(getDomain(), spfData, MacroExpand.DOMAIN);
084        }
085    
086        /**
087         * @see org.apache.james.jspf.terms.AMechanism#onDNSResponse(org.apache.james.jspf.core.DNSResponse, org.apache.james.jspf.core.SPFSession)
088         */
089        @SuppressWarnings("unchecked")
090            public DNSLookupContinuation onDNSResponse(DNSResponse response, SPFSession spfSession)
091            throws PermErrorException, TempErrorException, NoneException, NeutralException {
092            try {
093                
094                List<String> records = (List<String>) spfSession.getAttribute(ATTRIBUTE_CHECK_RECORDS);
095                List<String> mxR = (List<String>) spfSession.getAttribute(ATTRIBUTE_MX_RECORDS);
096    
097                if (records == null) {
098                
099                    records = response.getResponse();
100    
101                    if (records == null) {
102                        // no mx record found
103                        spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, Boolean.FALSE);
104                        return null;
105                    }
106                    
107                    spfSession.setAttribute(ATTRIBUTE_CHECK_RECORDS, records);
108                    
109                } else {
110                    
111                    List<String> res = response.getResponse();
112    
113                    if (res != null) {
114                        if (mxR == null) {
115                            mxR = new ArrayList<String>();
116                            spfSession.setAttribute(ATTRIBUTE_MX_RECORDS, mxR);
117                        }
118                        mxR.addAll(res);
119                    }
120                    
121                }
122    
123                // if the remote IP is an ipv6 we check ipv6 addresses, otherwise ip4
124                boolean isIPv6 = IPAddr.isIPV6(spfSession.getIpAddress());
125    
126                String mx;
127                while (records.size() > 0 && (mx = records.remove(0)) != null && mx.length() > 0) {
128                    log.debug("Add MX-Record " + mx + " to list");
129    
130                    return new DNSLookupContinuation(new DNSRequest(mx, isIPv6 ? DNSRequest.AAAA : DNSRequest.A), MXMechanism.this);
131                    
132                }
133                    
134                // no mx record found
135                if (mxR == null || mxR.size() == 0) {
136                    spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, Boolean.FALSE);
137                    return null;
138                }
139    
140                // get the ipAddress
141                IPAddr checkAddress;
142                checkAddress = IPAddr.getAddress(spfSession.getIpAddress(), isIPv6 ? getIp6cidr() : getIp4cidr());
143                
144                // clean up attributes
145                spfSession.removeAttribute(ATTRIBUTE_CHECK_RECORDS);
146                spfSession.removeAttribute(ATTRIBUTE_MX_RECORDS);
147                spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, Boolean.valueOf(checkAddressList(checkAddress, mxR, getIp4cidr())));
148                return null;
149                
150            } catch (TimeoutException e) {
151                spfSession.setAttribute(ATTRIBUTE_CHECK_RECORDS, null);
152                spfSession.setAttribute(ATTRIBUTE_MX_RECORDS, null);
153                throw new TempErrorException("Timeout querying the dns server");
154            }
155        }
156    
157        /**
158         * @see org.apache.james.jspf.terms.AMechanism#toString()
159         */
160        public String toString() {
161            return super.toString("mx");
162        }
163    
164    }