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 }