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 package org.apache.james.rrt.lib;
020
021 import java.util.ArrayList;
022 import java.util.Collection;
023 import java.util.Iterator;
024 import java.util.List;
025 import java.util.Map;
026 import java.util.regex.Pattern;
027 import java.util.regex.PatternSyntaxException;
028
029 import javax.annotation.Resource;
030 import javax.mail.internet.ParseException;
031
032 import org.apache.commons.configuration.ConfigurationException;
033 import org.apache.commons.configuration.HierarchicalConfiguration;
034 import org.apache.james.domainlist.api.DomainList;
035 import org.apache.james.domainlist.api.DomainListException;
036 import org.apache.james.lifecycle.api.Configurable;
037 import org.apache.james.lifecycle.api.LogEnabled;
038 import org.apache.james.rrt.api.RecipientRewriteTable;
039 import org.apache.james.rrt.api.RecipientRewriteTableException;
040 import org.apache.mailet.MailAddress;
041 import org.slf4j.Logger;
042
043 /**
044 *
045 */
046 public 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 @Resource(name = "domainlist")
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 Collection<String> getMappings(String user, String domain) throws ErrorMappingException, RecipientRewriteTableException {
111 return getMappings(user, domain, mappingLimit);
112 }
113
114 public Collection<String> 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 String targetString = mapAddress(user, domain);
122
123 // Only non-null mappings are translated
124 if (targetString != null) {
125 Collection<String> mappings = new ArrayList<String>();
126 if (targetString.startsWith(RecipientRewriteTable.ERROR_PREFIX)) {
127 throw new ErrorMappingException(targetString.substring(RecipientRewriteTable.ERROR_PREFIX.length()));
128
129 } else {
130 Iterator<String> map = RecipientRewriteTableUtil.mappingToCollection(targetString).iterator();
131
132 while (map.hasNext()) {
133 String target = map.next();
134
135 if (target.startsWith(RecipientRewriteTable.REGEX_PREFIX)) {
136 try {
137 target = RecipientRewriteTableUtil.regexMap(new MailAddress(user, domain), target);
138 } catch (PatternSyntaxException e) {
139 getLogger().error("Exception during regexMap processing: ", e);
140 } catch (ParseException e) {
141 // should never happen
142 getLogger().error("Exception during regexMap processing: ", e);
143 }
144 } else if (target.startsWith(RecipientRewriteTable.ALIASDOMAIN_PREFIX)) {
145 target = user + "@" + target.substring(RecipientRewriteTable.ALIASDOMAIN_PREFIX.length());
146 }
147
148 if (target == null)
149 continue;
150
151 StringBuffer buf = new StringBuffer().append("Valid virtual user mapping ").append(user).append("@").append(domain).append(" to ").append(target);
152 getLogger().debug(buf.toString());
153
154 if (recursive) {
155
156 String userName = null;
157 String domainName = null;
158 String args[] = target.split("@");
159
160 if (args != null && args.length > 1) {
161
162 userName = args[0];
163 domainName = args[1];
164 } else {
165 // TODO Is that the right todo here?
166 userName = target;
167 domainName = domain;
168 }
169
170 // Check if the returned mapping is the same as the
171 // input. If so return null to avoid loops
172 if (userName.equalsIgnoreCase(user) && domainName.equalsIgnoreCase(domain)) {
173 return null;
174 }
175
176 Collection<String> childMappings = getMappings(userName, domainName, mappingLimit - 1);
177
178 if (childMappings == null) {
179 // add mapping
180 mappings.add(target);
181 } else {
182 mappings.addAll(childMappings);
183 }
184
185 } else {
186 mappings.add(target);
187 }
188 }
189 }
190
191 return mappings;
192
193 }
194
195 return null;
196 }
197
198 /**
199 * @see org.apache.james.rrt.api.RecipientRewriteTable#addRegexMapping(java.lang.String,
200 * java.lang.String, java.lang.String)
201 */
202 public void addRegexMapping(String user, String domain, String regex) throws RecipientRewriteTableException {
203 try {
204 Pattern.compile(regex);
205 } catch (PatternSyntaxException e) {
206 throw new RecipientRewriteTableException("Invalid regex: " + regex, e);
207 }
208
209 checkMapping(user, domain, regex);
210 getLogger().info("Add regex mapping => " + regex + " for user: " + user + " domain: " + domain);
211 addMappingInternal(user, domain, RecipientRewriteTable.REGEX_PREFIX + regex);
212
213 }
214
215 /**
216 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeRegexMapping(java.lang.String,
217 * java.lang.String, java.lang.String)
218 */
219 public void removeRegexMapping(String user, String domain, String regex) throws RecipientRewriteTableException {
220 getLogger().info("Remove regex mapping => " + regex + " for user: " + user + " domain: " + domain);
221 removeMappingInternal(user, domain, RecipientRewriteTable.REGEX_PREFIX + regex);
222 }
223
224 /**
225 * @see org.apache.james.rrt.api.RecipientRewriteTable#addAddressMapping(java.lang.String,
226 * java.lang.String, java.lang.String)
227 */
228 public void addAddressMapping(String user, String domain, String address) throws RecipientRewriteTableException {
229 if (address.indexOf('@') < 0) {
230 try {
231 address = address + "@" + domainList.getDefaultDomain();
232 } catch (DomainListException e) {
233 throw new RecipientRewriteTableException("Unable to retrieve default domain", e);
234 }
235 }
236 try {
237 new MailAddress(address);
238 } catch (ParseException e) {
239 throw new RecipientRewriteTableException("Invalid emailAddress: " + address, e);
240 }
241 checkMapping(user, domain, address);
242 getLogger().info("Add address mapping => " + address + " for user: " + user + " domain: " + domain);
243 addMappingInternal(user, domain, address);
244
245 }
246
247 /**
248 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeAddressMapping(java.lang.String,
249 * java.lang.String, java.lang.String)
250 */
251 public void removeAddressMapping(String user, String domain, String address) throws RecipientRewriteTableException {
252 if (address.indexOf('@') < 0) {
253 try {
254 address = address + "@" + domainList.getDefaultDomain();
255 } catch (DomainListException e) {
256 throw new RecipientRewriteTableException("Unable to retrieve default domain", e);
257 }
258 }
259 getLogger().info("Remove address mapping => " + address + " for user: " + user + " domain: " + domain);
260 removeMappingInternal(user, domain, address);
261 }
262
263 /**
264 * @see org.apache.james.rrt.api.RecipientRewriteTable#addErrorMapping(java.lang.String,
265 * java.lang.String, java.lang.String)
266 */
267 public void addErrorMapping(String user, String domain, String error) throws RecipientRewriteTableException {
268 checkMapping(user, domain, error);
269 getLogger().info("Add error mapping => " + error + " for user: " + user + " domain: " + domain);
270 addMappingInternal(user, domain, RecipientRewriteTable.ERROR_PREFIX + error);
271
272 }
273
274 /**
275 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeErrorMapping(java.lang.String,
276 * java.lang.String, java.lang.String)
277 */
278 public void removeErrorMapping(String user, String domain, String error) throws RecipientRewriteTableException {
279 getLogger().info("Remove error mapping => " + error + " for user: " + user + " domain: " + domain);
280 removeMappingInternal(user, domain, RecipientRewriteTable.ERROR_PREFIX + error);
281 }
282
283 /**
284 * @see org.apache.james.rrt.api.RecipientRewriteTable#addMapping(java.lang.String,
285 * java.lang.String, java.lang.String)
286 */
287 public void addMapping(String user, String domain, String mapping) throws RecipientRewriteTableException {
288
289 String map = mapping.toLowerCase();
290
291 if (map.startsWith(RecipientRewriteTable.ERROR_PREFIX)) {
292 addErrorMapping(user, domain, map.substring(RecipientRewriteTable.ERROR_PREFIX.length()));
293 } else if (map.startsWith(RecipientRewriteTable.REGEX_PREFIX)) {
294 addRegexMapping(user, domain, map.substring(RecipientRewriteTable.REGEX_PREFIX.length()));
295 } else if (map.startsWith(RecipientRewriteTable.ALIASDOMAIN_PREFIX)) {
296 if (user != null)
297 throw new RecipientRewriteTableException("User must be null for aliasDomain mappings");
298 addAliasDomainMapping(domain, map.substring(RecipientRewriteTable.ALIASDOMAIN_PREFIX.length()));
299 } else {
300 addAddressMapping(user, domain, map);
301 }
302
303 }
304
305 /**
306 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeMapping(java.lang.String,
307 * java.lang.String, java.lang.String)
308 */
309 public void removeMapping(String user, String domain, String mapping) throws RecipientRewriteTableException {
310
311 String map = mapping.toLowerCase();
312
313 if (map.startsWith(RecipientRewriteTable.ERROR_PREFIX)) {
314 removeErrorMapping(user, domain, map.substring(RecipientRewriteTable.ERROR_PREFIX.length()));
315 } else if (map.startsWith(RecipientRewriteTable.REGEX_PREFIX)) {
316 removeRegexMapping(user, domain, map.substring(RecipientRewriteTable.REGEX_PREFIX.length()));
317 } else if (map.startsWith(RecipientRewriteTable.ALIASDOMAIN_PREFIX)) {
318 if (user != null)
319 throw new RecipientRewriteTableException("User must be null for aliasDomain mappings");
320 removeAliasDomainMapping(domain, map.substring(RecipientRewriteTable.ALIASDOMAIN_PREFIX.length()));
321 } else {
322 removeAddressMapping(user, domain, map);
323 }
324
325 }
326
327 /**
328 * @see org.apache.james.rrt.api.RecipientRewriteTable#getAllMappings()
329 */
330 public Map<String, Collection<String>> getAllMappings() throws RecipientRewriteTableException {
331 int count = 0;
332 Map<String, Collection<String>> mappings = getAllMappingsInternal();
333
334 if (mappings != null) {
335 count = mappings.size();
336 }
337 getLogger().debug("Retrieve all mappings. Mapping count: " + count);
338 return mappings;
339 }
340
341 /**
342 * @see org.apache.james.rrt.api.RecipientRewriteTable#getUserDomainMappings(java.lang.String,
343 * java.lang.String)
344 */
345 public Collection<String> getUserDomainMappings(String user, String domain) throws RecipientRewriteTableException {
346 return getUserDomainMappingsInternal(user, domain);
347 }
348
349 /**
350 * @see org.apache.james.rrt.api.RecipientRewriteTable#addAliasDomainMapping(java.lang.String,
351 * java.lang.String)
352 */
353 public void addAliasDomainMapping(String aliasDomain, String realDomain) throws RecipientRewriteTableException {
354 getLogger().info("Add domain mapping: " + aliasDomain + " => " + realDomain);
355 addMappingInternal(null, aliasDomain, RecipientRewriteTable.ALIASDOMAIN_PREFIX + realDomain);
356 }
357
358 /**
359 * @see org.apache.james.rrt.api.RecipientRewriteTable#removeAliasDomainMapping(java.lang.String,
360 * java.lang.String)
361 */
362 public void removeAliasDomainMapping(String aliasDomain, String realDomain) throws RecipientRewriteTableException {
363 getLogger().info("Remove domain mapping: " + aliasDomain + " => " + realDomain);
364 removeMappingInternal(null, aliasDomain, RecipientRewriteTable.ALIASDOMAIN_PREFIX + realDomain);
365 }
366
367 protected Logger getLogger() {
368 return logger;
369 }
370
371 /**
372 * Add new mapping
373 *
374 * @param user
375 * the user
376 * @param domain
377 * the domain
378 * @param mapping
379 * the mapping
380 * @throws InvalidMappingException
381 */
382 protected abstract void addMappingInternal(String user, String domain, String mapping) throws RecipientRewriteTableException;
383
384 /**
385 * Remove mapping
386 *
387 * @param user
388 * the user
389 * @param domain
390 * the domain
391 * @param mapping
392 * the mapping
393 * @throws InvalidMappingException
394 */
395 protected abstract void removeMappingInternal(String user, String domain, String mapping) throws RecipientRewriteTableException;
396
397 /**
398 * Return Collection of all mappings for the given username and domain
399 *
400 * @param user
401 * the user
402 * @param domain
403 * the domain
404 * @return Collection which hold the mappings
405 */
406 protected abstract Collection<String> getUserDomainMappingsInternal(String user, String domain) throws RecipientRewriteTableException;
407
408 /**
409 * Return a Map which holds all Mappings
410 *
411 * @return Map
412 */
413 protected abstract Map<String, Collection<String>> getAllMappingsInternal() throws RecipientRewriteTableException;
414
415 /**
416 * Override to map virtual recipients to real recipients, both local and
417 * non-local. Each key in the provided map corresponds to a potential
418 * virtual recipient, stored as a <code>MailAddress</code> object.
419 *
420 * Translate virtual recipients to real recipients by mapping a string
421 * containing the address of the real recipient as a value to a key. Leave
422 * the value <code>null<code>
423 * if no mapping should be performed. Multiple recipients may be specified by delineating
424 * the mapped string with commas, semi-colons or colons.
425 *
426 * @param user
427 * the mapping of virtual to real recipients, as
428 * <code>MailAddress</code>es to <code>String</code>s.
429 */
430 protected abstract String mapAddressInternal(String user, String domain) throws RecipientRewriteTableException;
431
432 /**
433 * Get all mappings for the given user and domain. If a aliasdomain mapping
434 * was found get sure it is in the map as first mapping.
435 *
436 * @param user
437 * the username
438 * @param domain
439 * the domain
440 * @return the mappings
441 */
442 private String mapAddress(String user, String domain) throws RecipientRewriteTableException {
443
444 String mappings = mapAddressInternal(user, domain);
445
446 // check if we need to sort
447 // TODO: Maybe we should just return the aliasdomain mapping
448 if (mappings != null && mappings.indexOf(RecipientRewriteTable.ALIASDOMAIN_PREFIX) > -1) {
449 Collection<String> mapCol = RecipientRewriteTableUtil.mappingToCollection(mappings);
450 Iterator<String> mapIt = mapCol.iterator();
451
452 List<String> col = new ArrayList<String>(mapCol.size());
453
454 while (mapIt.hasNext()) {
455 int i = 0;
456 String mapping = mapIt.next().toString();
457
458 if (mapping.startsWith(RecipientRewriteTable.ALIASDOMAIN_PREFIX)) {
459 col.add(i, mapping);
460 i++;
461 } else {
462 col.add(mapping);
463 }
464 }
465 return RecipientRewriteTableUtil.CollectionToMapping(col);
466 } else {
467 return mappings;
468 }
469 }
470
471 private void checkMapping(String user, String domain, String mapping) throws RecipientRewriteTableException {
472 Collection<String> mappings = getUserDomainMappings(user, domain);
473 if (mappings != null && mappings.contains(mapping)) {
474 throw new RecipientRewriteTableException("Mapping " + mapping + " for user " + user + " domain " + domain + " already exist!");
475 }
476 }
477
478 /**
479 * Return user String for the given argument.
480 * If give value is null, return a wildcard.
481 *
482 * @param user the given user String
483 * @return fixedUser the fixed user String
484 * @throws InvalidMappingException get thrown on invalid argument
485 */
486 protected String getFixedUser(String user) {
487 if (user != null) {
488 if (user.equals(WILDCARD) || user.indexOf("@") < 0) {
489 return user;
490 } else {
491 throw new IllegalArgumentException("Invalid user: " + user);
492 }
493 } else {
494 return WILDCARD;
495 }
496 }
497
498 /**
499 * Fix the domain for the given argument.
500 * If give value is null, return a wildcard.
501 *
502 * @param domain the given domain String
503 * @return fixedDomain the fixed domain String
504 * @throws InvalidMappingException get thrown on invalid argument
505 */
506 protected String getFixedDomain(String domain) {
507 if (domain != null) {
508 if (domain.equals(WILDCARD) || domain.indexOf("@") < 0) {
509 return domain;
510 } else {
511 throw new IllegalArgumentException("Invalid domain: " + domain);
512 }
513 } else {
514 return WILDCARD;
515 }
516 }
517
518 }