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.Inet6Util;
028    import org.apache.james.jspf.core.MacroExpand;
029    import org.apache.james.jspf.core.SPFChecker;
030    import org.apache.james.jspf.core.SPFCheckerDNSResponseListener;
031    import org.apache.james.jspf.core.SPFSession;
032    import org.apache.james.jspf.core.SPFTermsRegexps;
033    import org.apache.james.jspf.core.exceptions.NeutralException;
034    import org.apache.james.jspf.core.exceptions.NoneException;
035    import org.apache.james.jspf.core.exceptions.PermErrorException;
036    import org.apache.james.jspf.core.exceptions.TempErrorException;
037    import org.apache.james.jspf.core.exceptions.TimeoutException;
038    
039    import java.util.ArrayList;
040    import java.util.List;
041    
042    /**
043     * This class represent the a mechanism
044     * 
045     */
046    public class AMechanism extends GenericMechanism implements SPFCheckerDNSResponseListener {
047    
048        private static final String ATTRIBUTE_AMECHANISM_IPV4CHECK = "AMechanism.ipv4check";
049    
050        /**
051         * ABNF: A = "a" [ ":" domain-spec ] [ dual-cidr-length ]
052         */
053        public static final String REGEX = "[aA]" + "(?:\\:"
054                + SPFTermsRegexps.DOMAIN_SPEC_REGEX + ")?" + "(?:"
055                + DUAL_CIDR_LENGTH_REGEX + ")?";
056    
057        private int ip4cidr;
058    
059        private int ip6cidr;
060    
061        private SPFChecker expandedChecker = new ExpandedChecker();
062    
063        private final class ExpandedChecker implements SPFChecker {
064           /*
065            * (non-Javadoc)
066            * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
067            */
068            public DNSLookupContinuation checkSPF(SPFSession spfData) throws PermErrorException,
069                    TempErrorException, NeutralException, NoneException {
070                // Get the right host.
071                String host = expandHost(spfData);
072    
073                // get the ipAddress
074                try {
075                    boolean validIPV4Address = Inet6Util.isValidIPV4Address(spfData.getIpAddress());
076                    spfData.setAttribute(ATTRIBUTE_AMECHANISM_IPV4CHECK, Boolean.valueOf(validIPV4Address));
077                    if (validIPV4Address) {
078    
079                        List<String> aRecords = getARecords(host);
080                        if (aRecords == null) {
081                            try {
082                                DNSRequest request = new DNSRequest(host, DNSRequest.A);
083                                return new DNSLookupContinuation(request, AMechanism.this);
084                            } catch (NoneException e) {
085                                return onDNSResponse(new DNSResponse(aRecords), spfData);
086                            }
087                        } else {
088                            return onDNSResponse(new DNSResponse(aRecords), spfData);
089                        }
090             
091                    } else {
092                        
093                        List<String> aaaaRecords = getAAAARecords(host);
094                        if (aaaaRecords == null) {
095                            try {
096                                DNSRequest request = new DNSRequest(host, DNSRequest.AAAA);
097                                return new DNSLookupContinuation(request, AMechanism.this);
098                            } catch (NoneException e) {
099                                return onDNSResponse(new DNSResponse(aaaaRecords), spfData);
100                            }
101                        } else {
102                            return onDNSResponse(new DNSResponse(aaaaRecords), spfData);
103                        }
104    
105                    }
106                // PermError / TempError
107                // TODO: Should we replace this with the "right" Exceptions ?
108                } catch (Exception e) {
109                    log.debug("No valid ipAddress: ",e);
110                    throw new PermErrorException("No valid ipAddress: "
111                            + spfData.getIpAddress());
112                }
113                
114            }
115        }
116    
117        /**
118         * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
119         */
120        public DNSLookupContinuation checkSPF(SPFSession spfData) throws PermErrorException, TempErrorException, NeutralException, NoneException {
121            // update currentDepth
122            spfData.increaseCurrentDepth();
123    
124            spfData.pushChecker(expandedChecker);
125            
126            return macroExpand.checkExpand(getDomain(), spfData, MacroExpand.DOMAIN);
127        }
128    
129        /**
130         * @see org.apache.james.jspf.terms.GenericMechanism#config(Configuration)
131         */
132        public synchronized void config(Configuration params) throws PermErrorException {
133            super.config(params);
134            if (params.groupCount() >= 2 && params.group(2) != null) {
135                ip4cidr = Integer.parseInt(params.group(2));
136                if (ip4cidr > 32) {
137                    throw new PermErrorException("Ivalid IP4 CIDR length");
138                }
139            } else {
140                ip4cidr = 32;
141            }
142            if (params.groupCount() >= 3 && params.group(3) != null) {
143                ip6cidr = Integer.parseInt(params.group(3).toString());
144                if (ip6cidr > 128) {
145                    throw new PermErrorException("Ivalid IP6 CIDR length");
146                }
147            } else {
148                ip6cidr = 128;
149            }
150        }
151    
152        /**
153         * Check if the given ipaddress array contains the provided ip.
154         * 
155         * @param checkAddress
156         *            The ip wich should be contained in the given ArrayList
157         * @param addressList
158         *            The ip ArrayList.
159         * @return true or false
160         * @throws PermErrorException 
161         */
162        public boolean checkAddressList(IPAddr checkAddress, List<String> addressList, int cidr) throws PermErrorException {
163    
164            for (int i = 0; i < addressList.size(); i++) {
165                String ip = addressList.get(i);
166    
167                // Check for empty record
168                if (ip != null) {
169                    // set the mask in the address.
170                    // TODO should we use cidr from the parameters or the input checkAddress cidr?
171                    IPAddr ipAddr = IPAddr.getAddress(ip, checkAddress.getMaskLength());
172                    if (checkAddress.getMaskedIPAddress().equals(
173                            ipAddr.getMaskedIPAddress())) {
174                        return true;
175                    }
176                }
177            }
178            return false;
179        }
180    
181        /**
182         * @return Returns the ip4cidr.
183         */
184        protected synchronized int getIp4cidr() {
185            return ip4cidr;
186        }
187    
188        /**
189         * @return Returns the ip6cidr.
190         */
191        protected synchronized int getIp6cidr() {
192            return ip6cidr;
193        }
194    
195        /**
196         * @see java.lang.Object#toString()
197         */
198        public String toString() {
199            return toString("a");
200        }
201    
202        /**
203         * @see java.lang.Object#toString()
204         */
205        protected String toString(String mechKey) {
206            StringBuffer res = new StringBuffer();
207            res.append(mechKey);
208            if (getDomain() != null) {
209                res.append(":"+getDomain());
210            }
211            if (getIp4cidr() != 32) {
212                res.append("/"+getIp4cidr());
213            }
214            if (getIp6cidr() != 128) {
215                res.append("//"+getIp4cidr());
216            }
217            return res.toString();
218        }
219        
220        
221        /**
222         * Retrieve a list of AAAA records
223         */
224        public List<String> getAAAARecords(String strServer) {
225            List<String> listAAAAData = null;
226            if (IPAddr.isIPV6(strServer)) {
227                // Address is already an IP address, so add it to list
228                listAAAAData = new ArrayList<String>();
229                listAAAAData.add(strServer);
230            }
231            return listAAAAData;
232        }
233    
234    
235        /**
236         * Get a list of IPAddr's for a server
237         * 
238         * @param strServer
239         *            The hostname or ipAddress whe should get the A-Records for
240         * @return The ipAddresses
241         */
242        public List<String> getARecords(String strServer) {
243            List<String> listAData = null;
244            if (IPAddr.isIPAddr(strServer)) {
245                listAData = new ArrayList<String>();
246                listAData.add(strServer);
247            }
248            return listAData;
249        }
250    
251        /**
252         * @see org.apache.james.jspf.core.SPFCheckerDNSResponseListener#onDNSResponse(org.apache.james.jspf.core.DNSResponse, org.apache.james.jspf.core.SPFSession)
253         */
254        public DNSLookupContinuation onDNSResponse(DNSResponse response, SPFSession spfSession)
255            throws PermErrorException, TempErrorException, NoneException, NeutralException {
256            List<String> listAData = null;
257            try {
258                listAData = response.getResponse();
259            } catch (TimeoutException e) {
260                throw new TempErrorException("Timeout querying dns server");
261            }
262            // no a records just return null
263            if (listAData == null) {
264                spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, Boolean.FALSE);
265                return null;
266            }
267    
268            Boolean ipv4check = (Boolean) spfSession.getAttribute(ATTRIBUTE_AMECHANISM_IPV4CHECK);
269            if (ipv4check.booleanValue()) {
270    
271                IPAddr checkAddress = IPAddr.getAddress(spfSession.getIpAddress(),
272                        getIp4cidr());
273    
274                if (checkAddressList(checkAddress, listAData, getIp4cidr())) {
275                    spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, Boolean.TRUE);
276                    return null;
277                }
278    
279            } else {
280    
281                IPAddr checkAddress = IPAddr.getAddress(spfSession.getIpAddress(),
282                        getIp6cidr());
283                
284                if (checkAddressList(checkAddress, listAData, getIp6cidr())) {
285                    spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, Boolean.TRUE);
286                    return null;
287                }
288    
289            }
290            
291            spfSession.setAttribute(Directive.ATTRIBUTE_MECHANISM_RESULT, Boolean.FALSE);
292            return null;
293        }
294    
295    }