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 }