1 /************************************************************************
2 * Copyright (c) 1999-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.smtpserver;
19
20 import org.apache.avalon.framework.configuration.Configurable;
21 import org.apache.avalon.framework.configuration.Configuration;
22 import org.apache.avalon.framework.configuration.ConfigurationException;
23 import org.apache.avalon.framework.logger.AbstractLogEnabled;
24 import org.apache.james.util.mail.dsn.DSNStatus;
25 import org.apache.mailet.MailAddress;
26 import java.util.Collection;
27 import java.util.ArrayList;
28 import java.util.StringTokenizer;
29 import java.util.Locale;
30
31 /***
32 * Handles RCPT command
33 */
34 public class RcptCmdHandler
35 extends AbstractLogEnabled
36 implements CommandHandler,Configurable {
37
38 /***
39 * The keys used to store sender and recepients in the SMTPSession state
40 */
41 private final static String RCPTCOUNT = "RCPT_COUNT";
42 private int maxRcpt = 0;
43 private int tarpitRcptCount = 0;
44 private long tarpitSleepTime = 5000;
45
46 /***
47 * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
48 */
49 public void configure(Configuration handlerConfiguration) throws ConfigurationException {
50 Configuration configuration = handlerConfiguration.getChild("maxRcpt",false);
51 if(configuration != null) {
52 maxRcpt = configuration.getValueAsInteger();
53 }
54
55 Configuration configTarpitRcptCount = handlerConfiguration.getChild("tarpitRcptCount",false);
56 if(configTarpitRcptCount != null) {
57 tarpitRcptCount = configTarpitRcptCount.getValueAsInteger();
58 }
59
60 Configuration configTarpitSleepTime = handlerConfiguration.getChild("tarpitSleepTime",false);
61 if(configTarpitSleepTime != null) {
62 tarpitSleepTime = configTarpitSleepTime.getValueAsLong();
63 }
64 }
65
66
67
68
69
70
71 public void onCommand(SMTPSession session) {
72 doRCPT(session, session.getCommandArgument());
73 }
74
75
76 /***
77 * Handler method called upon receipt of a RCPT command.
78 * Reads recipient. Does some connection validation.
79 *
80 *
81 * @param session SMTP session object
82 * @param argument the argument passed in with the command by the SMTP client
83 */
84 private void doRCPT(SMTPSession session, String argument) {
85 String responseString = null;
86 StringBuffer responseBuffer = session.getResponseBuffer();
87 boolean maxRcptReached = false;
88 boolean useTarpit = false;
89
90 String recipient = null;
91 if ((argument != null) && (argument.indexOf(":") > 0)) {
92 int colonIndex = argument.indexOf(":");
93 recipient = argument.substring(colonIndex + 1);
94 argument = argument.substring(0, colonIndex);
95 }
96 if (!session.getState().containsKey(SMTPSession.SENDER)) {
97 responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Need MAIL before RCPT";
98 session.writeResponse(responseString);
99 } else if (argument == null || !argument.toUpperCase(Locale.US).equals("TO")
100 || recipient == null) {
101 responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_SYNTAX)+" Usage: RCPT TO:<recipient>";
102 session.writeResponse(responseString);
103 } else {
104 Collection rcptColl = (Collection) session.getState().get(SMTPSession.RCPT_LIST);
105 if (rcptColl == null) {
106 rcptColl = new ArrayList();
107 }
108 recipient = recipient.trim();
109 int lastChar = recipient.lastIndexOf('>');
110
111
112 String rcptOptionString = null;
113 if ((lastChar > 0) && (recipient.length() > lastChar + 2) && (recipient.charAt(lastChar + 1) == ' ')) {
114 rcptOptionString = recipient.substring(lastChar + 2);
115
116
117 recipient = recipient.substring(0, lastChar + 1);
118 }
119 if (!recipient.startsWith("<") || !recipient.endsWith(">")) {
120 responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_SYNTAX)+" Syntax error in parameters or arguments";
121 session.writeResponse(responseString);
122 if (getLogger().isErrorEnabled()) {
123 StringBuffer errorBuffer =
124 new StringBuffer(192)
125 .append("Error parsing recipient address: ")
126 .append("Address did not start and end with < >")
127 .append(getContext(session,null,recipient));
128 getLogger().error(errorBuffer.toString());
129 }
130 return;
131 }
132 MailAddress recipientAddress = null;
133
134 recipient = recipient.substring(1, recipient.length() - 1);
135 if (recipient.indexOf("@") < 0) {
136 recipient = recipient + "@localhost";
137 }
138
139 try {
140 recipientAddress = new MailAddress(recipient);
141 } catch (Exception pe) {
142
143
144
145
146
147 responseString = "553 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX)+" Syntax error in recipient address";
148 session.writeResponse(responseString);
149
150 if (getLogger().isErrorEnabled()) {
151 StringBuffer errorBuffer =
152 new StringBuffer(192)
153 .append("Error parsing recipient address: ")
154 .append(getContext(session,recipientAddress,recipient))
155 .append(pe.getMessage());
156 getLogger().error(errorBuffer.toString());
157 }
158 return;
159 }
160
161 if (session.isBlockListed() &&
162 !(session.isRelayingAllowed() || (session.isAuthRequired() && session.getUser() != null)) &&
163 !(recipientAddress.getUser().equalsIgnoreCase("postmaster") || recipientAddress.getUser().equalsIgnoreCase("abuse"))) {
164
165 responseString = "530 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Rejected: unauthenticated e-mail from " + session.getRemoteIPAddress() + " is restricted. Contact the postmaster for details.";
166 session.writeResponse(responseString);
167 return;
168 }
169
170 if (session.isAuthRequired() && !session.isRelayingAllowed()) {
171
172
173 if (session.getUser() == null) {
174 String toDomain = recipientAddress.getHost();
175 if (!session.getConfigurationData().getMailServer().isLocalServer(toDomain)) {
176 responseString = "530 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Authentication Required";
177 session.writeResponse(responseString);
178 StringBuffer sb = new StringBuffer(128);
179 sb.append("Rejected message - authentication is required for mail request");
180 sb.append(getContext(session,recipientAddress,recipient));
181 getLogger().error(sb.toString());
182 return;
183 }
184 } else {
185
186 if (session.getConfigurationData().isVerifyIdentity()) {
187 String authUser = (session.getUser()).toLowerCase(Locale.US);
188 MailAddress senderAddress = (MailAddress) session.getState().get(SMTPSession.SENDER);
189
190 if ((senderAddress == null) || (!authUser.equals(senderAddress.getUser())) ||
191 (!session.getConfigurationData().getMailServer().isLocalServer(senderAddress.getHost()))) {
192 responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Incorrect Authentication for Specified Email Address";
193 session.writeResponse(responseString);
194 if (getLogger().isErrorEnabled()) {
195 StringBuffer errorBuffer =
196 new StringBuffer(128)
197 .append("User ")
198 .append(authUser)
199 .append(" authenticated, however tried sending email as ")
200 .append(senderAddress)
201 .append(getContext(session,recipientAddress,recipient));
202 getLogger().error(errorBuffer.toString());
203 }
204 return;
205 }
206 }
207 }
208 } else if (!session.isRelayingAllowed()) {
209 String toDomain = recipientAddress.getHost();
210 if (!session.getConfigurationData().getMailServer().isLocalServer(toDomain)) {
211 responseString = "550 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Requested action not taken: relaying denied";
212 session.writeResponse(responseString);
213 StringBuffer errorBuffer = new StringBuffer(128)
214 .append("Rejected message - ")
215 .append(session.getRemoteIPAddress())
216 .append(" not authorized to relay to ")
217 .append(toDomain)
218 .append(getContext(session,recipientAddress,recipient));
219 getLogger().error(errorBuffer.toString());
220 return;
221 }
222 }
223 if (rcptOptionString != null) {
224
225 StringTokenizer optionTokenizer = new StringTokenizer(rcptOptionString, " ");
226 while (optionTokenizer.hasMoreElements()) {
227 String rcptOption = optionTokenizer.nextToken();
228 int equalIndex = rcptOption.indexOf('=');
229 String rcptOptionName = rcptOption;
230 String rcptOptionValue = "";
231 if (equalIndex > 0) {
232 rcptOptionName = rcptOption.substring(0, equalIndex).toUpperCase(Locale.US);
233 rcptOptionValue = rcptOption.substring(equalIndex + 1);
234 }
235
236 if (getLogger().isDebugEnabled()) {
237 StringBuffer debugBuffer =
238 new StringBuffer(128)
239 .append("RCPT command had unrecognized/unexpected option ")
240 .append(rcptOptionName)
241 .append(" with value ")
242 .append(rcptOptionValue)
243 .append(getContext(session,recipientAddress,recipient));
244 getLogger().debug(debugBuffer.toString());
245 }
246 }
247 optionTokenizer = null;
248 }
249
250
251 if (maxRcpt > 0) {
252 int rcptCount = 0;
253
254
255 rcptCount = getRcptCount(session);
256
257 rcptCount++;
258
259
260 if (rcptCount > maxRcpt) {
261 maxRcptReached = true;
262 responseString = "452 "+DSNStatus.getStatus(DSNStatus.NETWORK,DSNStatus.DELIVERY_TOO_MANY_REC)+" Requested action not taken: max recipients reached";
263 session.writeResponse(responseString);
264 getLogger().error(responseString);
265 }
266
267
268 session.getState().put(RCPTCOUNT,Integer.toString(rcptCount));
269 }
270
271
272 if (tarpitRcptCount > 0) {
273 int rcptCount = 0;
274 rcptCount = getRcptCount(session);
275 rcptCount++;
276
277 if (rcptCount > tarpitRcptCount) {
278 useTarpit = true;
279 }
280
281
282 session.getState().put(RCPTCOUNT,Integer.toString(rcptCount));
283
284 }
285
286 if (maxRcptReached == false) {
287 rcptColl.add(recipientAddress);
288 session.getState().put(SMTPSession.RCPT_LIST, rcptColl);
289 responseBuffer.append("250 "+DSNStatus.getStatus(DSNStatus.SUCCESS,DSNStatus.ADDRESS_VALID)+" Recipient <")
290 .append(recipient)
291 .append("> OK");
292 responseString = session.clearResponseBuffer();
293
294 if (useTarpit == true) {
295 try {
296 sleep(tarpitSleepTime);
297 } catch (InterruptedException e) { }
298 }
299 session.writeResponse(responseString);
300 }
301 }
302 }
303
304
305 private String getContext(SMTPSession session, MailAddress recipientAddress, String recipient){
306 StringBuffer sb = new StringBuffer(128);
307 if(null!=recipientAddress) {
308 sb.append(" [to:" + (recipientAddress).toInternetAddress().getAddress() + "]");
309 } else if(null!=recipient) {
310 sb.append(" [to:" + recipient + "]");
311 }
312 if (null!=session.getState().get(SMTPSession.SENDER)) {
313 sb.append(" [from:" + ((MailAddress)session.getState().get(SMTPSession.SENDER)).toInternetAddress().getAddress() + "]");
314 }
315 return sb.toString();
316 }
317
318
319 private int getRcptCount(SMTPSession session) {
320 int startCount = 0;
321
322
323 if (session.getState().get(RCPTCOUNT) != null) {
324 Integer rcptCountInteger = Integer.valueOf(session.getState().get(RCPTCOUNT).toString());
325 return rcptCountInteger.intValue();
326 } else {
327 return startCount;
328 }
329 }
330
331
332 public void sleep(float timeInMillis) throws InterruptedException {
333 Thread.sleep( (long) timeInMillis );
334 }
335 }