View Javadoc

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