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  
22  package org.apache.james.impl.vut;
23  
24  import java.net.InetAddress;
25  import java.net.UnknownHostException;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Map;
32  
33  import javax.mail.internet.ParseException;
34  
35  import org.apache.avalon.framework.configuration.Configurable;
36  import org.apache.avalon.framework.configuration.Configuration;
37  import org.apache.avalon.framework.configuration.ConfigurationException;
38  import org.apache.avalon.framework.logger.AbstractLogEnabled;
39  import org.apache.avalon.framework.logger.Logger;
40  import org.apache.avalon.framework.service.ServiceException;
41  import org.apache.avalon.framework.service.ServiceManager;
42  import org.apache.avalon.framework.service.Serviceable;
43  import org.apache.james.api.dnsservice.DNSService;
44  import org.apache.james.api.domainlist.DomainList;
45  import org.apache.james.api.vut.ErrorMappingException;
46  import org.apache.james.api.vut.VirtualUserTable;
47  import org.apache.james.api.vut.management.InvalidMappingException;
48  import org.apache.james.api.vut.management.VirtualUserTableManagement;
49  import org.apache.mailet.MailAddress;
50  import org.apache.oro.text.regex.MalformedPatternException;
51  import org.apache.oro.text.regex.Perl5Compiler;
52  
53  /**
54   * 
55   */
56  public abstract class AbstractVirtualUserTable extends AbstractLogEnabled
57      implements VirtualUserTable, VirtualUserTableManagement, DomainList, Serviceable, Configurable {
58      
59      private boolean autoDetect = true;
60      private boolean autoDetectIP = true;
61      private DNSService dns;
62      
63      // The maximum mappings which will process before throwing exception
64      private int mappingLimit = 10;
65         
66      // TODO: Should we use true or false as default ?
67      private boolean recursive = true;
68  
69      /**
70       * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
71       */
72      public void service(ServiceManager arg0) throws ServiceException {
73          dns = (DNSService)arg0.lookup(DNSService.ROLE); 
74      }
75      
76      
77      /**
78       * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
79       */
80      public void configure(Configuration arg0) throws ConfigurationException {
81          Configuration recursiveConf = arg0.getChild("recursiveMapping", false);
82  
83          if (recursiveConf != null) {
84              setRecursiveMapping(recursiveConf.getValueAsBoolean(true));
85          }
86          
87          Configuration mappingLimitConf = arg0.getChild("mappingLimit", false);
88          
89          if (mappingLimitConf != null )  {
90              try {
91                  setMappingLimit(mappingLimitConf.getValueAsInteger(10));
92              } catch (IllegalArgumentException e) {
93                  throw new ConfigurationException(e.getMessage());
94              }
95          }
96      }
97      
98      public void setRecursiveMapping(boolean recursive) {
99          this.recursive = recursive;
100     }
101     
102     /**
103      * Set the mappingLimit
104      * 
105      * @param mappingLimit the mappingLimit
106      * @throws IllegalArgumentException get thrown if mappingLimit smaller then 1 is used
107      */
108     public void setMappingLimit(int mappingLimit) throws IllegalArgumentException {
109         if (mappingLimit < 1) throw new IllegalArgumentException("The minimum mappingLimit is 1");
110         this.mappingLimit = mappingLimit;
111     }
112     
113     /**
114      * @see org.apache.james.api.vut.VirtualUserTable#getMappings(String, String)
115      */
116     public Collection getMappings(String user,String domain) throws ErrorMappingException {
117         return getMappings(user,domain,mappingLimit);
118     }
119     
120 
121     public Collection getMappings(String user,String domain,int mappingLimit) throws ErrorMappingException {
122 
123         // We have to much mappings throw ErrorMappingException to avoid infinity loop
124         if (mappingLimit == 0) throw new ErrorMappingException("554 Too many mappings to process");
125 
126         String targetString = mapAddress(user, domain);
127         
128         // Only non-null mappings are translated
129         if (targetString != null) {
130             Collection mappings = new ArrayList();
131             if (targetString.startsWith(VirtualUserTable.ERROR_PREFIX)) {
132                 throw new ErrorMappingException(targetString.substring(VirtualUserTable.ERROR_PREFIX.length()));
133 
134             } else {
135                 Iterator map = VirtualUserTableUtil.mappingToCollection(targetString).iterator();
136 
137                 while (map.hasNext()) {
138                     String target = map.next().toString();
139 
140                     if (target.startsWith(VirtualUserTable.REGEX_PREFIX)) {
141                         try {
142                             target = VirtualUserTableUtil.regexMap(new MailAddress(user,domain), target);
143                         } catch (MalformedPatternException e) {
144                             getLogger().error("Exception during regexMap processing: ", e);
145                         } catch (ParseException e) {
146                             // should never happen
147                             getLogger().error("Exception during regexMap processing: ", e);
148                         } 
149                     } else if (target.startsWith(VirtualUserTable.ALIASDOMAIN_PREFIX)) {
150                         target = user + "@" + target.substring(VirtualUserTable.ALIASDOMAIN_PREFIX.length());
151                     }
152 
153                     if (target == null) continue;
154                     
155                     StringBuffer buf = new StringBuffer().append("Valid virtual user mapping ")
156                                                          .append(user).append("@").append(domain)
157                                                          .append(" to ").append(target);
158                     getLogger().debug(buf.toString());
159                    
160                  
161                     if (recursive) {
162                     
163                         String userName = null;
164                         String domainName = null;
165                         String args[] = target.split("@");
166                                         
167                         if (args != null && args.length > 0) {
168                     
169                             userName = args[0];
170                             domainName = args[1];
171                         } else {
172                             // TODO Is that the right todo here?
173                             userName = target;
174                             domainName = domain;
175                         }
176                                         
177                         // Check if the returned mapping is the same as the input. If so return null to avoid loops
178                         if (userName.equalsIgnoreCase(user) && domainName.equalsIgnoreCase(domain)) {
179                             return null;
180                         }
181                                         
182                         Collection childMappings = getMappings(userName, domainName, mappingLimit -1);
183                     
184                         if (childMappings == null) {
185                              // add mapping
186                             mappings.add(target);
187                         } else {
188                             mappings.addAll(childMappings);         
189                         }
190                                         
191                     } else {
192                         mappings.add(target);
193                     }
194                 } 
195             }
196             return mappings;
197         }
198         return null;
199     }
200     
201     /**
202      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#addRegexMapping(java.lang.String, java.lang.String, java.lang.String)
203      */
204     public synchronized boolean addRegexMapping(String user, String domain, String regex) throws InvalidMappingException {     
205         try {
206             new Perl5Compiler().compile(regex);
207         } catch (MalformedPatternException e) {
208             throw new InvalidMappingException("Invalid regex: " + regex);
209         }
210         
211         if (checkMapping(user,domain,regex) == true) {
212             getLogger().info("Add regex mapping => " + regex + " for user: " + user + " domain: " + domain);
213             return addMappingInternal(user, domain, VirtualUserTable.REGEX_PREFIX + regex);
214         } else {
215             return false;
216         }
217     }
218 
219     
220     /**
221      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#removeRegexMapping(java.lang.String, java.lang.String, java.lang.String)
222      */
223     public synchronized boolean removeRegexMapping(String user, String domain, String regex) throws InvalidMappingException {
224         getLogger().info("Remove regex mapping => " + regex + " for user: " + user + " domain: " + domain);
225         return removeMappingInternal(user,domain,VirtualUserTable.REGEX_PREFIX + regex);
226     }
227     
228     /**
229      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#addAddressMapping(java.lang.String, java.lang.String, java.lang.String)
230      */
231     public synchronized boolean addAddressMapping(String user, String domain, String address) throws InvalidMappingException {
232         if (address.indexOf('@') < 0) {
233             address =  address + "@localhost";
234         } 
235         try {
236             new MailAddress(address);
237         } catch (ParseException e) {
238             throw new InvalidMappingException("Invalid emailAddress: " + address);
239         }
240         if (checkMapping(user,domain,address) == true) {          
241             getLogger().info("Add address mapping => " + address + " for user: " + user + " domain: " + domain);
242             return addMappingInternal(user, domain, address);
243         } else {
244             return false;
245         }   
246     }
247     
248     /**
249      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#removeAddressMapping(java.lang.String, java.lang.String, java.lang.String)
250      */
251     public synchronized boolean removeAddressMapping(String user, String domain, String address) throws InvalidMappingException {
252         if (address.indexOf('@') < 0) {
253             address =  address + "@localhost";
254         } 
255         getLogger().info("Remove address mapping => " + address + " for user: " + user + " domain: " + domain);
256         return removeMappingInternal(user,domain,address);
257     }
258     
259     /**
260      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#addErrorMapping(java.lang.String, java.lang.String, java.lang.String)
261      */
262     public synchronized boolean addErrorMapping(String user, String domain, String error) throws InvalidMappingException {   
263         if (checkMapping(user,domain,error) == true) {          
264             getLogger().info("Add error mapping => " + error + " for user: " + user + " domain: " + domain);
265             return addMappingInternal(user,domain, VirtualUserTable.ERROR_PREFIX + error);
266         } else {
267             return false;
268         } 
269     }
270     
271     /**
272      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#removeErrorMapping(java.lang.String, java.lang.String, java.lang.String)
273      */
274     public synchronized boolean removeErrorMapping(String user, String domain, String error) throws InvalidMappingException {
275         getLogger().info("Remove error mapping => " + error + " for user: " + user + " domain: " + domain);     
276         return removeMappingInternal(user,domain,VirtualUserTable.ERROR_PREFIX + error);
277     }
278 
279 
280     /**
281      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#addMapping(java.lang.String, java.lang.String, java.lang.String)
282      */
283     public synchronized boolean addMapping(String user, String domain, String mapping) throws InvalidMappingException {
284         String map = mapping.toLowerCase();
285         
286         if (map.startsWith(VirtualUserTable.ERROR_PREFIX)) {
287             return addErrorMapping(user,domain,map.substring(VirtualUserTable.ERROR_PREFIX.length()));
288         } else if (map.startsWith(VirtualUserTable.REGEX_PREFIX)) {
289             return addRegexMapping(user,domain,map.substring(VirtualUserTable.REGEX_PREFIX.length()));
290         } else if (map.startsWith(VirtualUserTable.ALIASDOMAIN_PREFIX)) {
291             if (user != null) throw new InvalidMappingException("User must be null for aliasDomain mappings");
292             return addAliasDomainMapping(domain,map.substring(VirtualUserTable.ALIASDOMAIN_PREFIX.length()));
293         } else {
294             return addAddressMapping(user,domain,map);
295         }
296     }
297     
298     /**
299      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#removeMapping(java.lang.String, java.lang.String, java.lang.String)
300      */
301     public synchronized boolean removeMapping(String user, String domain, String mapping) throws InvalidMappingException {
302         String map = mapping.toLowerCase();
303     
304         if (map.startsWith(VirtualUserTable.ERROR_PREFIX)) {
305             return removeErrorMapping(user,domain,map.substring(VirtualUserTable.ERROR_PREFIX.length()));
306         } else if (map.startsWith(VirtualUserTable.REGEX_PREFIX)) {
307             return removeRegexMapping(user,domain,map.substring(VirtualUserTable.REGEX_PREFIX.length()));
308         } else if (map.startsWith(VirtualUserTable.ALIASDOMAIN_PREFIX)) {
309             if (user != null) throw new InvalidMappingException("User must be null for aliasDomain mappings");
310             return removeAliasDomainMapping(domain,map.substring(VirtualUserTable.ALIASDOMAIN_PREFIX.length()));
311         } else {
312             return removeAddressMapping(user,domain,map);
313         }
314     }
315     
316     /**
317      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#getAllMappings()
318      */
319     public Map getAllMappings() {
320         int count = 0;
321         Map mappings = getAllMappingsInternal();
322     
323         if (mappings != null) {
324             count = mappings.size();
325         }
326         getLogger().debug("Retrieve all mappings. Mapping count: " + count);
327         return mappings;
328     }
329     
330     
331    private boolean checkMapping(String user,String domain, String mapping) {
332        Collection mappings = getUserDomainMappings(user,domain);
333        if (mappings != null && mappings.contains(mapping)) {
334            return false;
335        } else {
336            return true;
337        }
338    }
339 
340  
341     /**
342      * @see org.apache.james.api.domainlist.DomainList#getDomains()
343      */
344     public List getDomains() {
345         List domains = getDomainsInternal();
346         if (domains != null) {
347             
348             String hostName = null;
349             try {
350                 hostName = dns.getHostName(dns.getLocalHost());
351             } catch  (UnknownHostException ue) {
352                 hostName = "localhost";
353             }
354             
355             getLogger().info("Local host is: " + hostName);
356             
357             hostName = hostName.toLowerCase(Locale.US);
358             
359             if (autoDetect == true && hostName.equals("localhost") == false && domains.contains(hostName) == false) {
360                 domains.add(hostName);
361             }
362            
363             if (autoDetectIP == true) {
364                 List ipList = getDomainsIP(domains,dns,getLogger());
365                 for(int i = 0; i < ipList.size(); i++) {
366                     if (domains.contains(ipList.get(i)) == false) {
367                         domains.add(ipList.get(i));
368                     }
369                 }
370             }
371        
372             if (getLogger().isInfoEnabled()) {
373                 for (Iterator i = domains.iterator(); i.hasNext(); ) {
374                     getLogger().info("Handling mail for: " + i.next());
375                 }
376             }  
377             return domains;
378         } else {
379             return null;
380         }
381     }
382 
383     /**
384      * Return a List which holds all ipAddress of the domains in the given List
385      * 
386      * @param domains List of domains
387      * @return domainIP List of ipaddress for domains
388      */
389     private static List getDomainsIP(List domains,DNSService dns,Logger log) {
390         List domainIP = new ArrayList();
391         if (domains.size() > 0 ) {
392             for (int i = 0; i < domains.size(); i++) {
393                 List domList = getDomainIP(domains.get(i).toString(),dns,log);
394                 
395                 for(int i2 = 0; i2 < domList.size();i2++) {
396                     if(domainIP.contains(domList.get(i2)) == false) {
397                         domainIP.add(domList.get(i2));
398                     }
399                 }
400             }
401         }
402         return domainIP;    
403     }
404     
405     /**
406      * @see #getDomainsIP(List, DNSService, Logger)
407      */
408     private static List getDomainIP(String domain, DNSService dns, Logger log) {
409         List domainIP = new ArrayList();
410         try {
411             InetAddress[]  addrs = dns.getAllByName(domain);
412             for (int j = 0; j < addrs.length ; j++) {
413                 String ip = addrs[j].getHostAddress();
414                 if (domainIP.contains(ip) == false) {
415                     domainIP.add(ip);
416                 }
417             }
418         } catch (UnknownHostException e) {
419             log.error("Cannot get IP address(es) for " + domain);
420         }
421         return domainIP;
422     }
423 
424     /**
425      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#getUserDomainMappings(java.lang.String, java.lang.String)
426      */
427     public Collection getUserDomainMappings(String user, String domain) {
428         return getUserDomainMappingsInternal(user, domain);
429     }
430     
431     /**
432      * @see org.apache.james.api.domainlist.DomainList#setAutoDetect(boolean)
433      */
434     public synchronized void setAutoDetect(boolean autoDetect) {
435         getLogger().info("Set autodetect to: " + autoDetect);
436         this.autoDetect = autoDetect;
437     }
438     
439     /**
440      * @see org.apache.james.api.domainlist.DomainList#setAutoDetectIP(boolean)
441      */
442     public synchronized void setAutoDetectIP(boolean autoDetectIP) {
443         getLogger().info("Set autodetectIP to: " + autoDetectIP);
444         this.autoDetectIP = autoDetectIP;
445     }
446 
447     /**
448      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#addAliasDomainMapping(java.lang.String, java.lang.String)
449      */
450     public synchronized boolean addAliasDomainMapping(String aliasDomain, String realDomain) throws InvalidMappingException {
451         getLogger().info("Add domain mapping: " + aliasDomain  + " => " + realDomain);
452         return addMappingInternal(null, aliasDomain, VirtualUserTable.ALIASDOMAIN_PREFIX + realDomain);
453     }
454     
455     /**
456      * @see org.apache.james.api.vut.management.VirtualUserTableManagement#removeAliasDomainMapping(java.lang.String, java.lang.String)
457      */
458     public synchronized boolean removeAliasDomainMapping(String aliasDomain, String realDomain) throws InvalidMappingException {
459         getLogger().info("Remove domain mapping: " + aliasDomain  + " => " + realDomain);
460         return removeMappingInternal(null, aliasDomain, VirtualUserTable.ALIASDOMAIN_PREFIX + realDomain);
461     }
462     
463     /**
464      * Get all mappings for the given user and domain. If a aliasdomain mapping was found get sure it is in the map as first mapping. 
465      * 
466      * @param user the username
467      * @param domain the domain
468      * @return the mappings
469      */
470     private String mapAddress(String user,String domain) {
471        String mappings = mapAddressInternal(user, domain);
472 
473         // check if we need to sort
474         // TODO: Maybe we should just return the aliasdomain mapping
475         if (mappings != null && mappings.indexOf(VirtualUserTable.ALIASDOMAIN_PREFIX) > -1) {
476             Collection mapCol = VirtualUserTableUtil.mappingToCollection(mappings);
477             Iterator mapIt = mapCol.iterator();
478         
479             List col = new ArrayList(mapCol.size());
480         
481             while (mapIt.hasNext()) {
482                 int i = 0;
483                 String mapping = mapIt.next().toString();
484         
485                 if (mapping.startsWith(VirtualUserTable.ALIASDOMAIN_PREFIX)) {
486                     col.add(i,mapping);
487                     i++;
488                 } else {
489                     col.add(mapping);
490                 }
491             }
492             return VirtualUserTableUtil.CollectionToMapping(col);
493         } else {  
494             return mappings;
495         }
496     }
497       
498     
499     /**
500      * Add new mapping
501      * 
502      * @param user the user
503      * @param domain the domain
504      * @param mapping the mapping
505      * @return true if successfully
506      * @throws InvalidMappingException 
507      */
508     protected abstract boolean  addMappingInternal(String user, String domain, String mapping) throws InvalidMappingException;
509     
510     /**
511      * Remove mapping 
512      * 
513      * @param user the user
514      * @param domain the domain
515      * @param mapping the mapping 
516      * @return true if successfully
517      * @throws InvalidMappingException 
518      */
519     protected abstract boolean  removeMappingInternal(String user, String domain, String mapping) throws InvalidMappingException;
520 
521     /**
522      * Return List of all domains for which email should accepted
523      * 
524      * @return domains  the domains
525      */
526     protected abstract List getDomainsInternal();
527     
528     /**
529      * Return Collection of all mappings for the given username and domain
530      * 
531      * @param user the user
532      * @param domain the domain
533      * @return Collection which hold the mappings
534      */
535     protected abstract Collection getUserDomainMappingsInternal(String user, String domain);
536 
537     /**
538      * Return a Map which holds all Mappings
539      * 
540      * @return Map
541      */
542     protected abstract Map getAllMappingsInternal();
543     
544     /**
545      * Override to map virtual recipients to real recipients, both local and non-local.
546      * Each key in the provided map corresponds to a potential virtual recipient, stored as
547      * a <code>MailAddress</code> object.
548      * 
549      * Translate virtual recipients to real recipients by mapping a string containing the
550      * address of the real recipient as a value to a key. Leave the value <code>null<code>
551      * if no mapping should be performed. Multiple recipients may be specified by delineating
552      * the mapped string with commas, semi-colons or colons.
553      * 
554      * @param user the mapping of virtual to real recipients, as 
555      *    <code>MailAddress</code>es to <code>String</code>s.
556      */
557     protected abstract String mapAddressInternal(String user, String domain);
558 }