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.transport.mailets;
23  
24  import org.apache.james.Constants;
25  import org.apache.james.core.MailImpl;
26  import org.apache.james.impl.vut.VirtualUserTableUtil;
27  import org.apache.mailet.base.GenericMailet;
28  import org.apache.mailet.Mail;
29  import org.apache.mailet.MailAddress;
30  import org.apache.oro.text.regex.MalformedPatternException;
31  
32  import javax.mail.MessagingException;
33  import javax.mail.internet.ParseException;
34  
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.HashMap;
38  import java.util.HashSet;
39  import java.util.Iterator;
40  import java.util.Map;
41  import java.util.StringTokenizer;
42  
43  /**
44   * Provides an abstraction of common functionality needed for implementing
45   * a Virtual User Table. Override the <code>mapRecipients</code> method to
46   * map virtual recipients to real recipients.
47   */
48  public abstract class AbstractVirtualUserTable extends GenericMailet
49  {
50      static private final String MARKER = "org.apache.james.transport.mailets.AbstractVirtualUserTable.mapped";
51  
52      /**
53       * Checks the recipient list of the email for user mappings.  Maps recipients as
54       * appropriate, modifying the recipient list of the mail and sends mail to any new
55       * non-local recipients.
56       *
57       * @param mail the mail to process
58       */
59      public void service(Mail mail) throws MessagingException
60      {
61          if (mail.getAttribute(MARKER) != null) {
62              mail.removeAttribute(MARKER);
63              return;
64          }
65  
66          Collection recipientsToRemove = new HashSet();
67          Collection recipientsToAddLocal = new ArrayList();
68          Collection recipientsToAddForward = new ArrayList();
69  
70          Collection recipients = mail.getRecipients();
71          Map recipientsMap = new HashMap(recipients.size());
72  
73          for (Iterator iter = recipients.iterator(); iter.hasNext(); ) {
74              MailAddress address = (MailAddress)iter.next();
75  
76              // Assume all addresses are non-virtual at start
77              recipientsMap.put(address, null);
78          }
79  
80          mapRecipients(recipientsMap);
81  
82          for (Iterator iter = recipientsMap.keySet().iterator(); iter.hasNext(); ) {
83              MailAddress source = (MailAddress)iter.next();
84              String targetString = (String)recipientsMap.get(source);
85  
86              // Only non-null mappings are translated
87              if(targetString != null) {
88                  if (targetString.startsWith("error:")) {
89                      //Mark this source address as an address to remove from the recipient list
90                      recipientsToRemove.add(source);
91                      processDSN(mail, source, targetString);
92                  } else {
93                      StringTokenizer tokenizer = new StringTokenizer(targetString, getSeparator(targetString));
94  
95                      while (tokenizer.hasMoreTokens()) {
96                          String targetAddress = tokenizer.nextToken().trim();
97  
98                          // log("Attempting to map from " + source + " to " + targetAddress);
99  
100                         if (targetAddress.startsWith("regex:")) {
101                             try {
102                                 targetAddress = VirtualUserTableUtil.regexMap(source, targetAddress);
103                             } catch (MalformedPatternException e) {
104                                 log("Exception during regexMap processing: ", e);
105                             }
106                             if (targetAddress == null) continue;
107                         }
108 
109                         try {
110                             MailAddress target = (targetAddress.indexOf('@') < 0) ? new MailAddress(targetAddress, (String) getMailetContext().getAttribute(Constants.DEFAULT_DOMAIN))
111                                 : new MailAddress(targetAddress);
112 
113                             //Mark this source address as an address to remove from the recipient list
114                             recipientsToRemove.add(source);
115 
116                             // We need to separate local and remote
117                             // recipients.  This is explained below.
118                             if (getMailetContext().isLocalServer(target.getHost())) {
119                                 recipientsToAddLocal.add(target);
120                             } else {
121                                 recipientsToAddForward.add(target);
122                             }
123 
124                             StringBuffer buf = new StringBuffer().append("Translating virtual user ")
125                                                                  .append(source)
126                                                                  .append(" to ")
127                                                                  .append(target);
128                             log(buf.toString());
129 
130                         } catch (ParseException pe) {
131                             //Don't map this address... there's an invalid address mapping here
132                             StringBuffer exceptionBuffer =
133                                 new StringBuffer(128)
134                                 .append("There is an invalid map from ")
135                                 .append(source)
136                                 .append(" to ")
137                                 .append(targetAddress);
138                             log(exceptionBuffer.toString());
139                             continue;
140                         }
141                     }
142                 }
143             }
144         }
145 
146         // Remove mapped recipients
147         recipients.removeAll(recipientsToRemove);
148 
149         // Add mapped recipients that are local
150         recipients.addAll(recipientsToAddLocal);
151 
152         // We consider an address that we map to be, by definition, a
153         // local address.  Therefore if we mapped to a remote address,
154         // then we want to make sure that the mail can be relayed.
155         // However, the original e-mail would typically be subjected to
156         // relay testing.  By posting a new mail back through the
157         // system, we have a locally generated mail, which will not be
158         // subjected to relay testing.
159 
160         // Forward to mapped recipients that are remote
161         if (recipientsToAddForward.size() != 0) {
162             // Can't use this ... some mappings could lead to an infinite loop
163             // getMailetContext().sendMail(mail.getSender(), recipientsToAddForward, mail.getMessage());
164 
165             // duplicates the Mail object, to be able to modify the new mail keeping the original untouched
166             MailImpl newMail = new MailImpl(mail);
167             try {
168             newMail.setRemoteAddr(getMailetContext().getAttribute(Constants.HOSTADDRESS).toString());
169                 newMail.setRemoteHost(getMailetContext().getAttribute(Constants.HOSTNAME).toString());
170                 
171                 newMail.setRecipients(recipientsToAddForward);
172                 newMail.setAttribute(MARKER, Boolean.TRUE);
173                 getMailetContext().sendMail(newMail);
174             } finally {
175                 newMail.dispose();
176             }
177         }
178 
179         // If there are no recipients left, Ghost the message
180         if (recipients.size() == 0) {
181             mail.setState(Mail.GHOST);
182         }
183     }
184 
185     /**
186      * Override to map virtual recipients to real recipients, both local and non-local.
187      * Each key in the provided map corresponds to a potential virtual recipient, stored as
188      * a <code>MailAddress</code> object.
189      * 
190      * Translate virtual recipients to real recipients by mapping a string containing the
191      * address of the real recipient as a value to a key. Leave the value <code>null<code>
192      * if no mapping should be performed. Multiple recipients may be specified by delineating
193      * the mapped string with commas, semi-colons or colons.
194      * 
195      * @param recipientsMap the mapping of virtual to real recipients, as 
196      *    <code>MailAddress</code>es to <code>String</code>s.
197      */
198     protected abstract void mapRecipients(Map recipientsMap) throws MessagingException;
199   
200     /**
201      * Sends the message for DSN processing
202      *
203      * @param mail the Mail instance being processed
204      * @param address the MailAddress causing the DSN
205      * @param error a String in the form "error:<code> <msg>"
206      */
207     private void processDSN(Mail mail, MailAddress address, String error) {
208         // parse "error:<code> <msg>"
209       int msgPos = error.indexOf(' ');
210       try {
211           Integer code = Integer.valueOf(error.substring("error:".length(),msgPos));
212       } catch (NumberFormatException e) {
213           log("Cannot send DSN.  Exception parsing DSN code from: " + error, e);
214           return;
215       }
216       String msg = error.substring(msgPos + 1);
217       // process bounce for "source" address
218       try {
219           getMailetContext().bounce(mail, error);
220       }
221       catch (MessagingException me) {
222           log("Cannot send DSN.  Exception during DSN processing: ", me);
223       }
224   }
225 
226   /**
227    * Returns the character used to delineate multiple addresses.
228    * 
229    * @param targetString the string to parse
230    * @return the character to tokenize on
231    */
232   private String getSeparator(String targetString) {
233       return (targetString.indexOf(',') > -1 ? "," : (targetString.indexOf(';') > -1 ? ";" : (targetString.indexOf("regex:") > -1? "" : ":" )));
234   }
235 
236 }