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 ****************************************************************/ 019package org.apache.james.rrt.lib; 020 021import java.util.Locale; 022import java.util.Map; 023import java.util.regex.Pattern; 024import java.util.regex.PatternSyntaxException; 025 026import javax.inject.Inject; 027import javax.mail.internet.ParseException; 028 029import org.apache.commons.configuration.ConfigurationException; 030import org.apache.commons.configuration.HierarchicalConfiguration; 031import org.apache.james.domainlist.api.DomainList; 032import org.apache.james.domainlist.api.DomainListException; 033import org.apache.james.lifecycle.api.Configurable; 034import org.apache.james.lifecycle.api.LogEnabled; 035import org.apache.james.rrt.api.RecipientRewriteTable; 036import org.apache.james.rrt.api.RecipientRewriteTableException; 037import org.apache.james.rrt.lib.Mapping.Type; 038import org.apache.mailet.MailAddress; 039import org.slf4j.Logger; 040 041import com.google.common.annotations.VisibleForTesting; 042 043/** 044 * 045 */ 046public abstract class AbstractRecipientRewriteTable implements RecipientRewriteTable, LogEnabled, Configurable { 047 // The maximum mappings which will process before throwing exception 048 private int mappingLimit = 10; 049 050 private boolean recursive = true; 051 052 private Logger logger; 053 054 private DomainList domainList; 055 056 @Inject 057 public void setDomainList(DomainList domainList) { 058 this.domainList = domainList; 059 } 060 061 /** 062 * @see org.apache.james.lifecycle.api.Configurable#configure(HierarchicalConfiguration) 063 */ 064 public void configure(HierarchicalConfiguration config) throws ConfigurationException { 065 setRecursiveMapping(config.getBoolean("recursiveMapping", true)); 066 try { 067 setMappingLimit(config.getInt("mappingLimit", 10)); 068 } catch (IllegalArgumentException e) { 069 throw new ConfigurationException(e.getMessage()); 070 } 071 doConfigure(config); 072 } 073 074 public void setLog(Logger logger) { 075 this.logger = logger; 076 } 077 078 /** 079 * Override to handle config 080 * 081 * @param conf 082 * @throws ConfigurationException 083 */ 084 protected void doConfigure(HierarchicalConfiguration conf) throws ConfigurationException { 085 086 } 087 088 public void setRecursiveMapping(boolean recursive) { 089 this.recursive = recursive; 090 } 091 092 /** 093 * Set the mappingLimit 094 * 095 * @param mappingLimit 096 * the mappingLimit 097 * @throws IllegalArgumentException 098 * get thrown if mappingLimit smaller then 1 is used 099 */ 100 public void setMappingLimit(int mappingLimit) throws IllegalArgumentException { 101 if (mappingLimit < 1) 102 throw new IllegalArgumentException("The minimum mappingLimit is 1"); 103 this.mappingLimit = mappingLimit; 104 } 105 106 /** 107 * @see org.apache.james.rrt.api.RecipientRewriteTable#getMappings(String, 108 * String) 109 */ 110 public Mappings getMappings(String user, String domain) throws ErrorMappingException, RecipientRewriteTableException { 111 return getMappings(user, domain, mappingLimit); 112 } 113 114 public Mappings getMappings(String user, String domain, int mappingLimit) throws ErrorMappingException, RecipientRewriteTableException { 115 116 // We have to much mappings throw ErrorMappingException to avoid 117 // infinity loop 118 if (mappingLimit == 0) 119 throw new ErrorMappingException("554 Too many mappings to process"); 120 121 Mappings targetMappings = mapAddress(user, domain); 122 123 // Only non-null mappings are translated 124 if (targetMappings != null) { 125 if (targetMappings.contains(Type.Error)) { 126 throw new ErrorMappingException(targetMappings.getError().getErrorMessage()); 127 } else { 128 MappingsImpl.Builder mappings = MappingsImpl.builder(); 129 130 for (String target : targetMappings.asStrings()) { 131 if (target.startsWith(RecipientRewriteTable.REGEX_PREFIX)) { 132 try { 133 target = RecipientRewriteTableUtil.regexMap(new MailAddress(user, domain), target); 134 } catch (PatternSyntaxException e) { 135 getLogger().error("Exception during regexMap processing: ", e); 136 } catch (ParseException e) { 137 // should never happen 138 getLogger().error("Exception during regexMap processing: ", e); 139 } 140 } else if (target.startsWith(RecipientRewriteTable.ALIASDOMAIN_PREFIX)) { 141 target = user + "@" + target.substring(RecipientRewriteTable.ALIASDOMAIN_PREFIX.length()); 142 } 143 144 if (target == null) 145 continue; 146 147 String buf = "Valid virtual user mapping " + user + "@" + domain + " to " + target; 148 getLogger().debug(buf); 149 150 if (recursive) { 151 152 String userName; 153 String domainName; 154 String args[] = target.split("@"); 155 156 if (args != null && args.length > 1) { 157 158 userName = args[0]; 159 domainName = args[1]; 160 } else { 161 // TODO Is that the right todo here? 162 userName = target; 163 domainName = domain; 164 } 165 166 // Check if the returned mapping is the same as the 167 // input. If so return null to avoid loops 168 if (userName.equalsIgnoreCase(user) && domainName.equalsIgnoreCase(domain)) { 169 return null; 170 } 171 172 Mappings childMappings = getMappings(userName, domainName, mappingLimit - 1); 173 174 if (childMappings == null) { 175 // add mapping 176 mappings.add(target); 177 } else { 178 mappings = mappings.addAll(childMappings); 179 } 180 181 } else { 182 mappings.add(target); 183 } 184 } 185 return mappings.build(); 186 } 187 } 188 189 return null; 190 } 191 192 /** 193 * @see org.apache.james.rrt.api.RecipientRewriteTable#addRegexMapping(java.lang.String, 194 * java.lang.String, java.lang.String) 195 */ 196 public void addRegexMapping(String user, String domain, String regex) throws RecipientRewriteTableException { 197 try { 198 Pattern.compile(regex); 199 } catch (PatternSyntaxException e) { 200 throw new RecipientRewriteTableException("Invalid regex: " + regex, e); 201 } 202 203 checkMapping(user, domain, regex); 204 getLogger().info("Add regex mapping => " + regex + " for user: " + user + " domain: " + domain); 205 addMappingInternal(user, domain, RecipientRewriteTable.REGEX_PREFIX + regex); 206 207 } 208 209 /** 210 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeRegexMapping(java.lang.String, 211 * java.lang.String, java.lang.String) 212 */ 213 public void removeRegexMapping(String user, String domain, String regex) throws RecipientRewriteTableException { 214 getLogger().info("Remove regex mapping => " + regex + " for user: " + user + " domain: " + domain); 215 removeMappingInternal(user, domain, RecipientRewriteTable.REGEX_PREFIX + regex); 216 } 217 218 /** 219 * @see org.apache.james.rrt.api.RecipientRewriteTable#addAddressMapping(java.lang.String, 220 * java.lang.String, java.lang.String) 221 */ 222 public void addAddressMapping(String user, String domain, String address) throws RecipientRewriteTableException { 223 if (address.indexOf('@') < 0) { 224 try { 225 address = address + "@" + domainList.getDefaultDomain(); 226 } catch (DomainListException e) { 227 throw new RecipientRewriteTableException("Unable to retrieve default domain", e); 228 } 229 } 230 try { 231 new MailAddress(address); 232 } catch (ParseException e) { 233 throw new RecipientRewriteTableException("Invalid emailAddress: " + address, e); 234 } 235 checkMapping(user, domain, address); 236 getLogger().info("Add address mapping => " + address + " for user: " + user + " domain: " + domain); 237 addMappingInternal(user, domain, address); 238 239 } 240 241 /** 242 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeAddressMapping(java.lang.String, 243 * java.lang.String, java.lang.String) 244 */ 245 public void removeAddressMapping(String user, String domain, String address) throws RecipientRewriteTableException { 246 if (address.indexOf('@') < 0) { 247 try { 248 address = address + "@" + domainList.getDefaultDomain(); 249 } catch (DomainListException e) { 250 throw new RecipientRewriteTableException("Unable to retrieve default domain", e); 251 } 252 } 253 getLogger().info("Remove address mapping => " + address + " for user: " + user + " domain: " + domain); 254 removeMappingInternal(user, domain, address); 255 } 256 257 /** 258 * @see org.apache.james.rrt.api.RecipientRewriteTable#addErrorMapping(java.lang.String, 259 * java.lang.String, java.lang.String) 260 */ 261 public void addErrorMapping(String user, String domain, String error) throws RecipientRewriteTableException { 262 checkMapping(user, domain, error); 263 getLogger().info("Add error mapping => " + error + " for user: " + user + " domain: " + domain); 264 addMappingInternal(user, domain, RecipientRewriteTable.ERROR_PREFIX + error); 265 266 } 267 268 /** 269 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeErrorMapping(java.lang.String, 270 * java.lang.String, java.lang.String) 271 */ 272 public void removeErrorMapping(String user, String domain, String error) throws RecipientRewriteTableException { 273 getLogger().info("Remove error mapping => " + error + " for user: " + user + " domain: " + domain); 274 removeMappingInternal(user, domain, RecipientRewriteTable.ERROR_PREFIX + error); 275 } 276 277 /** 278 * @see org.apache.james.rrt.api.RecipientRewriteTable#addMapping(java.lang.String, 279 * java.lang.String, java.lang.String) 280 */ 281 public void addMapping(String user, String domain, String mapping) throws RecipientRewriteTableException { 282 283 String map = mapping.toLowerCase(Locale.US); 284 285 if (map.startsWith(RecipientRewriteTable.ERROR_PREFIX)) { 286 addErrorMapping(user, domain, map.substring(RecipientRewriteTable.ERROR_PREFIX.length())); 287 } else if (map.startsWith(RecipientRewriteTable.REGEX_PREFIX)) { 288 addRegexMapping(user, domain, map.substring(RecipientRewriteTable.REGEX_PREFIX.length())); 289 } else if (map.startsWith(RecipientRewriteTable.ALIASDOMAIN_PREFIX)) { 290 if (user != null) 291 throw new RecipientRewriteTableException("User must be null for aliasDomain mappings"); 292 addAliasDomainMapping(domain, map.substring(RecipientRewriteTable.ALIASDOMAIN_PREFIX.length())); 293 } else { 294 addAddressMapping(user, domain, map); 295 } 296 297 } 298 299 /** 300 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeMapping(java.lang.String, 301 * java.lang.String, java.lang.String) 302 */ 303 public void removeMapping(String user, String domain, String mapping) throws RecipientRewriteTableException { 304 305 String map = mapping.toLowerCase(Locale.US); 306 307 if (map.startsWith(RecipientRewriteTable.ERROR_PREFIX)) { 308 removeErrorMapping(user, domain, map.substring(RecipientRewriteTable.ERROR_PREFIX.length())); 309 } else if (map.startsWith(RecipientRewriteTable.REGEX_PREFIX)) { 310 removeRegexMapping(user, domain, map.substring(RecipientRewriteTable.REGEX_PREFIX.length())); 311 } else if (map.startsWith(RecipientRewriteTable.ALIASDOMAIN_PREFIX)) { 312 if (user != null) 313 throw new RecipientRewriteTableException("User must be null for aliasDomain mappings"); 314 removeAliasDomainMapping(domain, map.substring(RecipientRewriteTable.ALIASDOMAIN_PREFIX.length())); 315 } else { 316 removeAddressMapping(user, domain, map); 317 } 318 319 } 320 321 /** 322 * @see org.apache.james.rrt.api.RecipientRewriteTable#getAllMappings() 323 */ 324 public Map<String, Mappings> getAllMappings() throws RecipientRewriteTableException { 325 int count = 0; 326 Map<String, Mappings> mappings = getAllMappingsInternal(); 327 328 if (mappings != null) { 329 count = mappings.size(); 330 } 331 getLogger().debug("Retrieve all mappings. Mapping count: " + count); 332 return mappings; 333 } 334 335 /** 336 * @see org.apache.james.rrt.api.RecipientRewriteTable#getUserDomainMappings(java.lang.String, 337 * java.lang.String) 338 */ 339 public Mappings getUserDomainMappings(String user, String domain) throws RecipientRewriteTableException { 340 return getUserDomainMappingsInternal(user, domain); 341 } 342 343 /** 344 * @see org.apache.james.rrt.api.RecipientRewriteTable#addAliasDomainMapping(java.lang.String, 345 * java.lang.String) 346 */ 347 public void addAliasDomainMapping(String aliasDomain, String realDomain) throws RecipientRewriteTableException { 348 getLogger().info("Add domain mapping: " + aliasDomain + " => " + realDomain); 349 addMappingInternal(null, aliasDomain, RecipientRewriteTable.ALIASDOMAIN_PREFIX + realDomain); 350 } 351 352 /** 353 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeAliasDomainMapping(java.lang.String, 354 * java.lang.String) 355 */ 356 public void removeAliasDomainMapping(String aliasDomain, String realDomain) throws RecipientRewriteTableException { 357 getLogger().info("Remove domain mapping: " + aliasDomain + " => " + realDomain); 358 removeMappingInternal(null, aliasDomain, RecipientRewriteTable.ALIASDOMAIN_PREFIX + realDomain); 359 } 360 361 protected Logger getLogger() { 362 return logger; 363 } 364 365 /** 366 * Add new mapping 367 * 368 * @param user 369 * the user 370 * @param domain 371 * the domain 372 * @param mapping 373 * the mapping 374 * @throws RecipientRewriteTableException 375 */ 376 protected abstract void addMappingInternal(String user, String domain, String mapping) throws RecipientRewriteTableException; 377 378 /** 379 * Remove mapping 380 * 381 * @param user 382 * the user 383 * @param domain 384 * the domain 385 * @param mapping 386 * the mapping 387 * @throws RecipientRewriteTableException 388 */ 389 protected abstract void removeMappingInternal(String user, String domain, String mapping) throws RecipientRewriteTableException; 390 391 /** 392 * Return Collection of all mappings for the given username and domain 393 * 394 * @param user 395 * the user 396 * @param domain 397 * the domain 398 * @return Collection which hold the mappings 399 */ 400 protected abstract Mappings getUserDomainMappingsInternal(String user, String domain) throws RecipientRewriteTableException; 401 402 /** 403 * Return a Map which holds all Mappings 404 * 405 * @return Map 406 */ 407 protected abstract Map<String, Mappings> getAllMappingsInternal() throws RecipientRewriteTableException; 408 409 /** 410 * Override to map virtual recipients to real recipients, both local and 411 * non-local. Each key in the provided map corresponds to a potential 412 * virtual recipient, stored as a <code>MailAddress</code> object. 413 * 414 * Translate virtual recipients to real recipients by mapping a string 415 * containing the address of the real recipient as a value to a key. Leave 416 * the value <code>null<code> 417 * if no mapping should be performed. Multiple recipients may be specified by delineating 418 * the mapped string with commas, semi-colons or colons. 419 * 420 * @param user 421 * the mapping of virtual to real recipients, as 422 * <code>MailAddress</code>es to <code>String</code>s. 423 */ 424 protected abstract String mapAddressInternal(String user, String domain) throws RecipientRewriteTableException; 425 426 /** 427 * Get all mappings for the given user and domain. If a aliasdomain mapping 428 * was found get sure it is in the map as first mapping. 429 * 430 * @param user 431 * the username 432 * @param domain 433 * the domain 434 * @return the mappings 435 */ 436 private Mappings mapAddress(String user, String domain) throws RecipientRewriteTableException { 437 438 String mappings = mapAddressInternal(user, domain); 439 440 if (mappings != null) { 441 return sortMappings(MappingsImpl.fromRawString(mappings)); 442 } else { 443 return null; 444 } 445 } 446 447 @VisibleForTesting static Mappings sortMappings(Mappings mappings) { 448 if (mappings.contains(Mapping.Type.Domain)) { 449 return 450 MappingsImpl.builder() 451 .addAll(mappings.select(Mapping.Type.Domain)) 452 .addAll(mappings.exclude(Mapping.Type.Domain)) 453 .build(); 454 } else { 455 return mappings; 456 } 457 } 458 459 private void checkMapping(String user, String domain, String mapping) throws RecipientRewriteTableException { 460 Mappings mappings = getUserDomainMappings(user, domain); 461 if (mappings != null && mappings.contains(mapping)) { 462 throw new RecipientRewriteTableException("Mapping " + mapping + " for user " + user + " domain " + domain + " already exist!"); 463 } 464 } 465 466 /** 467 * Return user String for the given argument. 468 * If give value is null, return a wildcard. 469 * 470 * @param user the given user String 471 * @return fixedUser the fixed user String 472 */ 473 protected String getFixedUser(String user) { 474 if (user != null) { 475 if (user.equals(WILDCARD) || !user.contains("@")) { 476 return user; 477 } else { 478 throw new IllegalArgumentException("Invalid user: " + user); 479 } 480 } else { 481 return WILDCARD; 482 } 483 } 484 485 /** 486 * Fix the domain for the given argument. 487 * If give value is null, return a wildcard. 488 * 489 * @param domain the given domain String 490 * @return fixedDomain the fixed domain String 491 */ 492 protected String getFixedDomain(String domain) { 493 if (domain != null) { 494 if (domain.equals(WILDCARD) || !domain.contains("@")) { 495 return domain; 496 } else { 497 throw new IllegalArgumentException("Invalid domain: " + domain); 498 } 499 } else { 500 return WILDCARD; 501 } 502 } 503 504}