001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.smtpserver;
019:
020: import org.apache.avalon.framework.configuration.Configurable;
021: import org.apache.avalon.framework.configuration.Configuration;
022: import org.apache.avalon.framework.configuration.ConfigurationException;
023: import org.apache.avalon.framework.logger.AbstractLogEnabled;
024: import org.apache.james.util.mail.dsn.DSNStatus;
025: import org.apache.mailet.MailAddress;
026: import java.util.Collection;
027: import java.util.ArrayList;
028: import java.util.StringTokenizer;
029: import java.util.Locale;
030:
031: /**
032: * Handles RCPT command
033: */
034: public class RcptCmdHandler extends AbstractLogEnabled implements
035: CommandHandler, Configurable {
036:
037: /**
038: * The keys used to store sender and recepients in the SMTPSession state
039: */
040: private final static String RCPTCOUNT = "RCPT_COUNT";
041: private int maxRcpt = 0;
042: private int tarpitRcptCount = 0;
043: private long tarpitSleepTime = 5000;
044:
045: /**
046: * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
047: */
048: public void configure(Configuration handlerConfiguration)
049: throws ConfigurationException {
050: Configuration configuration = handlerConfiguration.getChild(
051: "maxRcpt", false);
052: if (configuration != null) {
053: maxRcpt = configuration.getValueAsInteger();
054: }
055:
056: Configuration configTarpitRcptCount = handlerConfiguration
057: .getChild("tarpitRcptCount", false);
058: if (configTarpitRcptCount != null) {
059: tarpitRcptCount = configTarpitRcptCount.getValueAsInteger();
060: }
061:
062: Configuration configTarpitSleepTime = handlerConfiguration
063: .getChild("tarpitSleepTime", false);
064: if (configTarpitSleepTime != null) {
065: tarpitSleepTime = configTarpitSleepTime.getValueAsLong();
066: }
067: }
068:
069: /*
070: * handles RCPT command
071: *
072: * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
073: **/
074: public void onCommand(SMTPSession session) {
075: doRCPT(session, session.getCommandArgument());
076: }
077:
078: /**
079: * Handler method called upon receipt of a RCPT command.
080: * Reads recipient. Does some connection validation.
081: *
082: *
083: * @param session SMTP session object
084: * @param argument the argument passed in with the command by the SMTP client
085: */
086: private void doRCPT(SMTPSession session, String argument) {
087: String responseString = null;
088: StringBuffer responseBuffer = session.getResponseBuffer();
089: boolean maxRcptReached = false;
090: boolean useTarpit = false;
091:
092: String recipient = null;
093: if ((argument != null) && (argument.indexOf(":") > 0)) {
094: int colonIndex = argument.indexOf(":");
095: recipient = argument.substring(colonIndex + 1);
096: argument = argument.substring(0, colonIndex);
097: }
098: if (!session.getState().containsKey(SMTPSession.SENDER)) {
099: responseString = "503 "
100: + DSNStatus.getStatus(DSNStatus.PERMANENT,
101: DSNStatus.DELIVERY_OTHER)
102: + " Need MAIL before RCPT";
103: session.writeResponse(responseString);
104: } else if (argument == null
105: || !argument.toUpperCase(Locale.US).equals("TO")
106: || recipient == null) {
107: responseString = "501 "
108: + DSNStatus.getStatus(DSNStatus.PERMANENT,
109: DSNStatus.DELIVERY_SYNTAX)
110: + " Usage: RCPT TO:<recipient>";
111: session.writeResponse(responseString);
112: } else {
113: Collection rcptColl = (Collection) session.getState().get(
114: SMTPSession.RCPT_LIST);
115: if (rcptColl == null) {
116: rcptColl = new ArrayList();
117: }
118: recipient = recipient.trim();
119: int lastChar = recipient.lastIndexOf('>');
120: // Check to see if any options are present and, if so, whether they are correctly formatted
121: // (separated from the closing angle bracket by a ' ').
122: String rcptOptionString = null;
123: if ((lastChar > 0) && (recipient.length() > lastChar + 2)
124: && (recipient.charAt(lastChar + 1) == ' ')) {
125: rcptOptionString = recipient.substring(lastChar + 2);
126:
127: // Remove the options from the recipient
128: recipient = recipient.substring(0, lastChar + 1);
129: }
130: if (!recipient.startsWith("<") || !recipient.endsWith(">")) {
131: responseString = "501 "
132: + DSNStatus.getStatus(DSNStatus.PERMANENT,
133: DSNStatus.DELIVERY_SYNTAX)
134: + " Syntax error in parameters or arguments";
135: session.writeResponse(responseString);
136: if (getLogger().isErrorEnabled()) {
137: StringBuffer errorBuffer = new StringBuffer(192)
138: .append("Error parsing recipient address: ")
139: .append(
140: "Address did not start and end with < >")
141: .append(
142: getContext(session, null, recipient));
143: getLogger().error(errorBuffer.toString());
144: }
145: return;
146: }
147: MailAddress recipientAddress = null;
148: //Remove < and >
149: recipient = recipient.substring(1, recipient.length() - 1);
150: if (recipient.indexOf("@") < 0) {
151: recipient = recipient + "@localhost";
152: }
153:
154: try {
155: recipientAddress = new MailAddress(recipient);
156: } catch (Exception pe) {
157: /*
158: * from RFC2822;
159: * 553 Requested action not taken: mailbox name not allowed
160: * (e.g., mailbox syntax incorrect)
161: */
162: responseString = "553 "
163: + DSNStatus.getStatus(DSNStatus.PERMANENT,
164: DSNStatus.ADDRESS_SYNTAX)
165: + " Syntax error in recipient address";
166: session.writeResponse(responseString);
167:
168: if (getLogger().isErrorEnabled()) {
169: StringBuffer errorBuffer = new StringBuffer(192)
170: .append("Error parsing recipient address: ")
171: .append(
172: getContext(session,
173: recipientAddress, recipient))
174: .append(pe.getMessage());
175: getLogger().error(errorBuffer.toString());
176: }
177: return;
178: }
179:
180: if (session.isBlockListed()
181: && // was found in the RBL
182: !(session.isRelayingAllowed() || (session
183: .isAuthRequired() && session.getUser() != null))
184: && // Not (either an authorized IP or (SMTP AUTH is enabled and not authenticated))
185: !(recipientAddress.getUser().equalsIgnoreCase(
186: "postmaster") || recipientAddress.getUser()
187: .equalsIgnoreCase("abuse"))) {
188: // trying to send e-mail to other than postmaster or abuse
189: responseString = "530 "
190: + DSNStatus.getStatus(DSNStatus.PERMANENT,
191: DSNStatus.SECURITY_AUTH)
192: + " Rejected: unauthenticated e-mail from "
193: + session.getRemoteIPAddress()
194: + " is restricted. Contact the postmaster for details.";
195: session.writeResponse(responseString);
196: return;
197: }
198:
199: if (session.isAuthRequired()
200: && !session.isRelayingAllowed()) {
201: // Make sure the mail is being sent locally if not
202: // authenticated else reject.
203: if (session.getUser() == null) {
204: String toDomain = recipientAddress.getHost();
205: if (!session.getConfigurationData().getMailServer()
206: .isLocalServer(toDomain)) {
207: responseString = "530 "
208: + DSNStatus.getStatus(
209: DSNStatus.PERMANENT,
210: DSNStatus.SECURITY_AUTH)
211: + " Authentication Required";
212: session.writeResponse(responseString);
213: StringBuffer sb = new StringBuffer(128);
214: sb
215: .append("Rejected message - authentication is required for mail request");
216: sb.append(getContext(session, recipientAddress,
217: recipient));
218: getLogger().error(sb.toString());
219: return;
220: }
221: } else {
222: // Identity verification checking
223: if (session.getConfigurationData()
224: .isVerifyIdentity()) {
225: String authUser = (session.getUser())
226: .toLowerCase(Locale.US);
227: MailAddress senderAddress = (MailAddress) session
228: .getState().get(SMTPSession.SENDER);
229:
230: if ((senderAddress == null)
231: || (!authUser.equals(senderAddress
232: .getUser()))
233: || (!session
234: .getConfigurationData()
235: .getMailServer()
236: .isLocalServer(
237: senderAddress.getHost()))) {
238: responseString = "503 "
239: + DSNStatus.getStatus(
240: DSNStatus.PERMANENT,
241: DSNStatus.SECURITY_AUTH)
242: + " Incorrect Authentication for Specified Email Address";
243: session.writeResponse(responseString);
244: if (getLogger().isErrorEnabled()) {
245: StringBuffer errorBuffer = new StringBuffer(
246: 128)
247: .append("User ")
248: .append(authUser)
249: .append(
250: " authenticated, however tried sending email as ")
251: .append(senderAddress)
252: .append(
253: getContext(
254: session,
255: recipientAddress,
256: recipient));
257: getLogger().error(
258: errorBuffer.toString());
259: }
260: return;
261: }
262: }
263: }
264: } else if (!session.isRelayingAllowed()) {
265: String toDomain = recipientAddress.getHost();
266: if (!session.getConfigurationData().getMailServer()
267: .isLocalServer(toDomain)) {
268: responseString = "550 "
269: + DSNStatus.getStatus(DSNStatus.PERMANENT,
270: DSNStatus.SECURITY_AUTH)
271: + " Requested action not taken: relaying denied";
272: session.writeResponse(responseString);
273: StringBuffer errorBuffer = new StringBuffer(128)
274: .append("Rejected message - ")
275: .append(session.getRemoteIPAddress())
276: .append(" not authorized to relay to ")
277: .append(toDomain)
278: .append(
279: getContext(session,
280: recipientAddress, recipient));
281: getLogger().error(errorBuffer.toString());
282: return;
283: }
284: }
285: if (rcptOptionString != null) {
286:
287: StringTokenizer optionTokenizer = new StringTokenizer(
288: rcptOptionString, " ");
289: while (optionTokenizer.hasMoreElements()) {
290: String rcptOption = optionTokenizer.nextToken();
291: int equalIndex = rcptOption.indexOf('=');
292: String rcptOptionName = rcptOption;
293: String rcptOptionValue = "";
294: if (equalIndex > 0) {
295: rcptOptionName = rcptOption.substring(0,
296: equalIndex).toUpperCase(Locale.US);
297: rcptOptionValue = rcptOption
298: .substring(equalIndex + 1);
299: }
300: // Unexpected option attached to the RCPT command
301: if (getLogger().isDebugEnabled()) {
302: StringBuffer debugBuffer = new StringBuffer(128)
303: .append(
304: "RCPT command had unrecognized/unexpected option ")
305: .append(rcptOptionName).append(
306: " with value ").append(
307: rcptOptionValue).append(
308: getContext(session,
309: recipientAddress,
310: recipient));
311: getLogger().debug(debugBuffer.toString());
312: }
313: }
314: optionTokenizer = null;
315: }
316:
317: // check if we should check for max recipients
318: if (maxRcpt > 0) {
319: int rcptCount = 0;
320:
321: // check if the key exists
322: rcptCount = getRcptCount(session);
323:
324: rcptCount++;
325:
326: // check if the max recipients has reached
327: if (rcptCount > maxRcpt) {
328: maxRcptReached = true;
329: responseString = "452 "
330: + DSNStatus.getStatus(DSNStatus.NETWORK,
331: DSNStatus.DELIVERY_TOO_MANY_REC)
332: + " Requested action not taken: max recipients reached";
333: session.writeResponse(responseString);
334: getLogger().error(responseString);
335: }
336:
337: // put the recipient cound in session hashtable
338: session.getState().put(RCPTCOUNT,
339: Integer.toString(rcptCount));
340: }
341:
342: // check if we should use tarpit
343: if (tarpitRcptCount > 0) {
344: int rcptCount = 0;
345: rcptCount = getRcptCount(session);
346: rcptCount++;
347:
348: if (rcptCount > tarpitRcptCount) {
349: useTarpit = true;
350: }
351:
352: // put the recipient cound in session hashtable
353: session.getState().put(RCPTCOUNT,
354: Integer.toString(rcptCount));
355:
356: }
357:
358: if (maxRcptReached == false) {
359: rcptColl.add(recipientAddress);
360: session.getState().put(SMTPSession.RCPT_LIST, rcptColl);
361: responseBuffer.append(
362: "250 "
363: + DSNStatus.getStatus(
364: DSNStatus.SUCCESS,
365: DSNStatus.ADDRESS_VALID)
366: + " Recipient <").append(recipient)
367: .append("> OK");
368: responseString = session.clearResponseBuffer();
369:
370: if (useTarpit == true) {
371: try {
372: sleep(tarpitSleepTime);
373: } catch (InterruptedException e) {
374: }
375: }
376: session.writeResponse(responseString);
377: }
378: }
379: }
380:
381: private String getContext(SMTPSession session,
382: MailAddress recipientAddress, String recipient) {
383: StringBuffer sb = new StringBuffer(128);
384: if (null != recipientAddress) {
385: sb.append(" [to:"
386: + (recipientAddress).toInternetAddress()
387: .getAddress() + "]");
388: } else if (null != recipient) {
389: sb.append(" [to:" + recipient + "]");
390: }
391: if (null != session.getState().get(SMTPSession.SENDER)) {
392: sb.append(" [from:"
393: + ((MailAddress) session.getState().get(
394: SMTPSession.SENDER)).toInternetAddress()
395: .getAddress() + "]");
396: }
397: return sb.toString();
398: }
399:
400: private int getRcptCount(SMTPSession session) {
401: int startCount = 0;
402:
403: // check if the key exists
404: if (session.getState().get(RCPTCOUNT) != null) {
405: Integer rcptCountInteger = Integer.valueOf(session
406: .getState().get(RCPTCOUNT).toString());
407: return rcptCountInteger.intValue();
408: } else {
409: return startCount;
410: }
411: }
412:
413: public void sleep(float timeInMillis) throws InterruptedException {
414: Thread.sleep((long) timeInMillis);
415: }
416: }
|