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 }