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