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    }