1 /************************************************************************
2 * Copyright (c) 2000-2006 The Apache Software Foundation. *
3 * All rights reserved. *
4 * ------------------------------------------------------------------- *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you *
6 * may not use this file except in compliance with the License. You *
7 * may obtain a copy of the License at: *
8 * *
9 * http://www.apache.org/licenses/LICENSE-2.0 *
10 * *
11 * Unless required by applicable law or agreed to in writing, software *
12 * distributed under the License is distributed on an "AS IS" BASIS, *
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14 * implied. See the License for the specific language governing *
15 * permissions and limitations under the License. *
16 ***********************************************************************/
17
18 package org.apache.james.transport.mailets;
19
20 import org.apache.james.core.MailImpl;
21 import org.apache.james.util.XMLResources;
22 import org.apache.mailet.GenericMailet;
23 import org.apache.mailet.Mail;
24 import org.apache.mailet.MailAddress;
25 import org.apache.oro.text.regex.MatchResult;
26 import org.apache.oro.text.regex.Pattern;
27 import org.apache.oro.text.regex.Perl5Compiler;
28 import org.apache.oro.text.regex.Perl5Matcher;
29
30 import javax.mail.MessagingException;
31 import javax.mail.internet.ParseException;
32
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Iterator;
38 import java.util.Map;
39 import java.util.StringTokenizer;
40
41 /***
42 * Provides an abstraction of common functionality needed for implementing
43 * a Virtual User Table. Override the <code>mapRecipients</code> method to
44 * map virtual recipients to real recipients.
45 */
46 public abstract class AbstractVirtualUserTable extends GenericMailet
47 {
48 static private final String MARKER = "org.apache.james.transport.mailets.AbstractVirtualUserTable.mapped";
49
50 /***
51 * Checks the recipient list of the email for user mappings. Maps recipients as
52 * appropriate, modifying the recipient list of the mail and sends mail to any new
53 * non-local recipients.
54 *
55 * @param mail the mail to process
56 */
57 public void service(Mail mail) throws MessagingException
58 {
59 if (mail.getAttribute(MARKER) != null) {
60 mail.removeAttribute(MARKER);
61 return;
62 }
63
64 Collection recipientsToRemove = new HashSet();
65 Collection recipientsToAddLocal = new ArrayList();
66 Collection recipientsToAddForward = new ArrayList();
67
68 Collection recipients = mail.getRecipients();
69 Map recipientsMap = new HashMap(recipients.size());
70
71 for (Iterator iter = recipients.iterator(); iter.hasNext(); ) {
72 MailAddress address = (MailAddress)iter.next();
73
74
75 recipientsMap.put(address, null);
76 }
77
78 mapRecipients(recipientsMap);
79
80 for (Iterator iter = recipientsMap.keySet().iterator(); iter.hasNext(); ) {
81 MailAddress source = (MailAddress)iter.next();
82 String targetString = (String)recipientsMap.get(source);
83
84
85 if(targetString != null) {
86 if (targetString.startsWith("error:")) {
87
88 recipientsToRemove.add(source);
89 processDSN(mail, source, targetString);
90 } else {
91 StringTokenizer tokenizer = new StringTokenizer(targetString, getSeparator(targetString));
92
93 while (tokenizer.hasMoreTokens()) {
94 String targetAddress = tokenizer.nextToken().trim();
95
96
97
98 if (targetAddress.startsWith("regex:")) {
99 targetAddress = regexMap(mail, source, targetAddress);
100 if (targetAddress == null) continue;
101 }
102
103 try {
104 MailAddress target = (targetAddress.indexOf('@') < 0) ? new MailAddress(targetAddress, "localhost")
105 : new MailAddress(targetAddress);
106
107
108 recipientsToRemove.add(source);
109
110
111
112 if (getMailetContext().isLocalServer(target.getHost())) {
113 recipientsToAddLocal.add(target);
114 } else {
115 recipientsToAddForward.add(target);
116 }
117
118 StringBuffer buf = new StringBuffer().append("Translating virtual user ")
119 .append(source)
120 .append(" to ")
121 .append(target);
122 log(buf.toString());
123
124 } catch (ParseException pe) {
125
126 StringBuffer exceptionBuffer =
127 new StringBuffer(128)
128 .append("There is an invalid map from ")
129 .append(source)
130 .append(" to ")
131 .append(targetAddress);
132 log(exceptionBuffer.toString());
133 continue;
134 }
135 }
136 }
137 }
138 }
139
140
141 recipients.removeAll(recipientsToRemove);
142
143
144 recipients.addAll(recipientsToAddLocal);
145
146
147
148
149
150
151
152
153
154
155 if (recipientsToAddForward.size() != 0) {
156
157
158
159
160 MailImpl newMail = new MailImpl(mail,newName(mail));
161 try {
162 try {
163 newMail.setRemoteAddr(java.net.InetAddress.getLocalHost().getHostAddress());
164 newMail.setRemoteHost(java.net.InetAddress.getLocalHost().getHostName());
165 } catch (java.net.UnknownHostException _) {
166 newMail.setRemoteAddr("127.0.0.1");
167 newMail.setRemoteHost("localhost");
168 }
169 newMail.setRecipients(recipientsToAddForward);
170 newMail.setAttribute(MARKER, Boolean.TRUE);
171 getMailetContext().sendMail(newMail);
172 } finally {
173 newMail.dispose();
174 }
175 }
176
177
178 if (recipients.size() == 0) {
179 mail.setState(Mail.GHOST);
180 }
181 }
182
183 /***
184 * Override to map virtual recipients to real recipients, both local and non-local.
185 * Each key in the provided map corresponds to a potential virtual recipient, stored as
186 * a <code>MailAddress</code> object.
187 *
188 * Translate virtual recipients to real recipients by mapping a string containing the
189 * address of the real recipient as a value to a key. Leave the value <code>null<code>
190 * if no mapping should be performed. Multiple recipients may be specified by delineating
191 * the mapped string with commas, semi-colons or colons.
192 *
193 * @param recipientsMap the mapping of virtual to real recipients, as
194 * <code>MailAddress</code>es to <code>String</code>s.
195 */
196 protected abstract void mapRecipients(Map recipientsMap) throws MessagingException;
197
198 /***
199 * Sends the message for DSN processing
200 *
201 * @param mail the Mail instance being processed
202 * @param address the MailAddress causing the DSN
203 * @param error a String in the form "error:<code> <msg>"
204 */
205 private void processDSN(Mail mail, MailAddress address, String error) {
206
207 int msgPos = error.indexOf(' ');
208 try {
209 Integer code = Integer.valueOf(error.substring("error:".length(),msgPos));
210 } catch (NumberFormatException e) {
211 log("Cannot send DSN. Exception parsing DSN code from: " + error, e);
212 return;
213 }
214 String msg = error.substring(msgPos + 1);
215
216 try {
217 getMailetContext().bounce(mail, error);
218 }
219 catch (MessagingException me) {
220 log("Cannot send DSN. Exception during DSN processing: ", me);
221 }
222 }
223
224 /***
225 * Processes regex virtual user mapping
226 *
227 * If a mapped target string begins with the prefix regex:, it must be
228 * formatted as regex:<regular-expression>:<parameterized-string>,
229 * e.g., regex:(.*)@(.*):${1}@tld
230 *
231 * @param mail the Mail instance being processed
232 * @param address the MailAddress to be mapped
233 * @param targetString a String specifying the mapping
234 */
235 private String regexMap(Mail mail, MailAddress address, String targetString) {
236 String result = null;
237
238 try {
239 int msgPos = targetString.indexOf(':', "regex:".length() + 1);
240
241
242
243
244
245
246
247 Pattern pattern = new Perl5Compiler().compile(targetString.substring("regex:".length(), msgPos));
248 Perl5Matcher matcher = new Perl5Matcher();
249
250 if (matcher.matches(address.toString(), pattern)) {
251 MatchResult match = matcher.getMatch();
252 Map parameters = new HashMap(match.groups());
253 for (int i = 1; i < match.groups(); i++) {
254 parameters.put(Integer.toString(i), match.group(i));
255 }
256 result = XMLResources.replaceParameters(targetString.substring(msgPos + 1), parameters);
257 }
258 }
259 catch (Exception e) {
260 log("Exception during regexMap processing: ", e);
261 }
262
263
264 return result;
265 }
266
267 /***
268 * Returns the character used to delineate multiple addresses.
269 *
270 * @param targetString the string to parse
271 * @return the character to tokenize on
272 */
273 private String getSeparator(String targetString) {
274 return (targetString.indexOf(',') > -1 ? "," : (targetString.indexOf(';') > -1 ? ";" : (targetString.indexOf("regex:") > -1? "" : ":" )));
275 }
276
277 private static final java.util.Random random = new java.util.Random();
278
279 /***
280 * Create a unique new primary key name.
281 *
282 * @param mail the mail to use as the basis for the new mail name
283 * @return a new name
284 */
285 private String newName(Mail mail) throws MessagingException {
286 String oldName = mail.getName();
287
288
289
290
291
292 if (oldName.length() > 76) {
293 int count = 0;
294 int index = 0;
295 while ((index = oldName.indexOf('!', index + 1)) >= 0) {
296 count++;
297 }
298
299 if (count > 7) {
300 throw new MessagingException("Unable to create a new message name: too long. Possible loop in config.xml.");
301 }
302 else {
303 oldName = oldName.substring(0, 76);
304 }
305 }
306
307 StringBuffer nameBuffer =
308 new StringBuffer(64)
309 .append(oldName)
310 .append("-!")
311 .append(random.nextInt(1048576));
312 return nameBuffer.toString();
313 }
314 }