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.IPAddr;
27  import org.apache.james.jspf.core.Inet6Util;
28  import org.apache.james.jspf.core.MacroExpand;
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.ArrayList;
40  import java.util.List;
41  
42  /**
43   * This class represent the a mechanism
44   * 
45   */
46  public class AMechanism extends GenericMechanism implements SPFCheckerDNSResponseListener {
47  
48      private static final String ATTRIBUTE_AMECHANISM_IPV4CHECK = "AMechanism.ipv4check";
49  
50      /**
51       * ABNF: A = "a" [ ":" domain-spec ] [ dual-cidr-length ]
52       */
53      public static final String REGEX = "[aA]" + "(?:\\:"
54              + SPFTermsRegexps.DOMAIN_SPEC_REGEX + ")?" + "(?:"
55              + DUAL_CIDR_LENGTH_REGEX + ")?";
56  
57      private int ip4cidr;
58  
59      private int ip6cidr;
60  
61      private SPFChecker expandedChecker = new ExpandedChecker();
62  
63      private final class ExpandedChecker implements SPFChecker {
64         /*
65          * (non-Javadoc)
66          * @see org.apache.james.jspf.core.SPFChecker#checkSPF(org.apache.james.jspf.core.SPFSession)
67          */
68          public DNSLookupContinuation checkSPF(SPFSession spfData) throws PermErrorException,
69                  TempErrorException, NeutralException, NoneException {
70              // Get the right host.
71              String host = expandHost(spfData);
72  
73              // get the ipAddress
74              try {
75                  boolean validIPV4Address = Inet6Util.isValidIPV4Address(spfData.getIpAddress());
76                  spfData.setAttribute(ATTRIBUTE_AMECHANISM_IPV4CHECK, Boolean.valueOf(validIPV4Address));
77                  if (validIPV4Address) {
78  
79                      List aRecords = getARecords(host);
80                      if (aRecords == null) {
81                          try {
82                              DNSRequest request = new DNSRequest(host, DNSRequest.A);
83                              return new DNSLookupContinuation(request, AMechanism.this);
84                          } catch (NoneException e) {
85                              return onDNSResponse(new DNSResponse(aRecords), spfData);
86                          }
87                      } else {
88                          return onDNSResponse(new DNSResponse(aRecords), spfData);
89                      }
90           
91                  } else {
92                      
93                      List aaaaRecords = getAAAARecords(host);
94                      if (aaaaRecords == null) {
95                          try {
96                              DNSRequest request = new DNSRequest(host, DNSRequest.AAAA);
97                              return new DNSLookupContinuation(request, AMechanism.this);
98                          } catch (NoneException e) {
99                              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 addressList, int cidr) throws PermErrorException {
163 
164         for (int i = 0; i < addressList.size(); i++) {
165             String ip = (String) 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 getAAAARecords(String strServer) {
225         List listAAAAData = null;
226         if (IPAddr.isIPV6(strServer)) {
227             // Address is already an IP address, so add it to list
228             listAAAAData = new ArrayList();
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 getARecords(String strServer) {
243         List listAData = null;
244         if (IPAddr.isIPAddr(strServer)) {
245             listAData = new ArrayList();
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 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 }