View Javadoc

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       * handles RCPT command
68       *
69       * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
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             // Check to see if any options are present and, if so, whether they are correctly formatted
111             // (separated from the closing angle bracket by a ' ').
112             String rcptOptionString = null;
113             if ((lastChar > 0) && (recipient.length() > lastChar + 2) && (recipient.charAt(lastChar + 1) == ' ')) {
114                 rcptOptionString = recipient.substring(lastChar + 2);
115 
116                 // Remove the options from the recipient
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             //Remove < and >
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                  * from RFC2822;
144                  * 553 Requested action not taken: mailbox name not allowed
145                  *     (e.g., mailbox syntax incorrect)
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() &&                                                // was found in the RBL
162                 !(session.isRelayingAllowed() || (session.isAuthRequired() && session.getUser() != null)) &&  // Not (either an authorized IP or (SMTP AUTH is enabled and not authenticated))
163                 !(recipientAddress.getUser().equalsIgnoreCase("postmaster") || recipientAddress.getUser().equalsIgnoreCase("abuse"))) {
164                 // trying to send e-mail to other than postmaster or abuse
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                 // Make sure the mail is being sent locally if not
172                 // authenticated else reject.
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                     // Identity verification checking
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                   // Unexpected option attached to the RCPT command
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             // check if we should check for max recipients
251             if (maxRcpt > 0) {
252                 int rcptCount = 0;
253             
254                 // check if the key exists
255                 rcptCount = getRcptCount(session);
256                 
257                 rcptCount++;
258         
259                 // check if the max recipients has reached
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                 // put the recipient cound in session hashtable
268                 session.getState().put(RCPTCOUNT,Integer.toString(rcptCount));
269             }
270             
271             // check if we should use tarpit
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                 // put the recipient cound in session hashtable
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         // check if the key exists
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 }