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  
21  
22  package org.apache.james.smtpserver.core;
23  
24  import org.apache.james.dsn.DSNStatus;
25  import org.apache.james.smtpserver.CommandHandler;
26  import org.apache.james.smtpserver.SMTPSession;
27  import org.apache.james.util.codec.Base64;
28  import org.apache.avalon.framework.logger.AbstractLogEnabled;
29  
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.Locale;
33  import java.util.StringTokenizer;
34  import java.io.IOException;
35  
36  
37  /**
38    * handles AUTH command
39    */
40  public class AuthCmdHandler
41      extends AbstractLogEnabled
42      implements CommandHandler {
43  
44      /**
45       * The text string for the SMTP AUTH type PLAIN.
46       */
47      private final static String AUTH_TYPE_PLAIN = "PLAIN";
48  
49      /**
50       * The text string for the SMTP AUTH type LOGIN.
51       */
52      private final static String AUTH_TYPE_LOGIN = "LOGIN";
53  
54      /**
55       * handles AUTH command
56       *
57       * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
58       */
59      public void onCommand(SMTPSession session) {
60          //deviation from the Main code
61          //Instead of throwing exception just end the session
62          try{
63              doAUTH(session, session.getCommandArgument());
64          } catch (Exception ex) {
65              getLogger().error("Exception occured:" + ex.getMessage());
66              session.endSession();
67          }
68      }
69  
70  
71  
72      /**
73       * Handler method called upon receipt of a AUTH command.
74       * Handles client authentication to the SMTP server.
75       *
76       * @param session SMTP session
77       * @param argument the argument passed in with the command by the SMTP client
78       */
79      private void doAUTH(SMTPSession session, String argument)
80              throws Exception {
81          String responseString = null;
82          if (session.getUser() != null) {
83              responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" User has previously authenticated. "
84                          + " Further authentication is not required!";
85              session.writeResponse(responseString);
86          } else if (argument == null) {
87              responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_INVALID_ARG)+" Usage: AUTH (authentication type) <challenge>";
88              session.writeResponse(responseString);
89          } else {
90              String initialResponse = null;
91              if ((argument != null) && (argument.indexOf(" ") > 0)) {
92                  initialResponse = argument.substring(argument.indexOf(" ") + 1);
93                  argument = argument.substring(0,argument.indexOf(" "));
94              }
95              String authType = argument.toUpperCase(Locale.US);
96              if (authType.equals(AUTH_TYPE_PLAIN)) {
97                  doPlainAuth(session, initialResponse);
98                  return;
99              } else if (authType.equals(AUTH_TYPE_LOGIN)) {
100                 doLoginAuth(session, initialResponse);
101                 return;
102             } else {
103                 doUnknownAuth(session, authType, initialResponse);
104                 return;
105             }
106         }
107     }
108 
109     /**
110      * Carries out the Plain AUTH SASL exchange.
111      *
112      * According to RFC 2595 the client must send: [authorize-id] \0 authenticate-id \0 password.
113      *
114      * >>> AUTH PLAIN dGVzdAB0ZXN0QHdpei5leGFtcGxlLmNvbQB0RXN0NDI=
115      * Decoded: test\000test@wiz.example.com\000tEst42
116      *
117      * >>> AUTH PLAIN dGVzdAB0ZXN0AHRFc3Q0Mg==
118      * Decoded: test\000test\000tEst42
119      *
120      * @param session SMTP session object
121      * @param initialResponse the initial response line passed in with the AUTH command
122      */
123     private void doPlainAuth(SMTPSession session, String initialResponse)
124             throws IOException {
125         String userpass = null, user = null, pass = null, responseString = null;
126         if (initialResponse == null) {
127             responseString = "334 OK. Continue authentication";
128             session.writeResponse(responseString);
129             userpass = session.readCommandLine();
130         } else {
131             userpass = initialResponse.trim();
132         }
133         try {
134             if (userpass != null) {
135                 userpass = Base64.decodeAsString(userpass);
136             }
137             if (userpass != null) {
138                 /*  See: RFC 2595, Section 6
139                     The mechanism consists of a single message from the client to the
140                     server.  The client sends the authorization identity (identity to
141                     login as), followed by a US-ASCII NUL character, followed by the
142                     authentication identity (identity whose password will be used),
143                     followed by a US-ASCII NUL character, followed by the clear-text
144                     password.  The client may leave the authorization identity empty to
145                     indicate that it is the same as the authentication identity.
146 
147                     The server will verify the authentication identity and password with
148                     the system authentication database and verify that the authentication
149                     credentials permit the client to login as the authorization identity.
150                     If both steps succeed, the user is logged in.
151                 */
152                 StringTokenizer authTokenizer = new StringTokenizer(userpass, "\0");
153                 String authorize_id = authTokenizer.nextToken();  // Authorization Identity
154                 user = authTokenizer.nextToken();                 // Authentication Identity
155                 try {
156                     pass = authTokenizer.nextToken();             // Password
157                 }
158                 catch (java.util.NoSuchElementException _) {
159                     // If we got here, this is what happened.  RFC 2595
160                     // says that "the client may leave the authorization
161                     // identity empty to indicate that it is the same as
162                     // the authentication identity."  As noted above,
163                     // that would be represented as a decoded string of
164                     // the form: "\0authenticate-id\0password".  The
165                     // first call to nextToken will skip the empty
166                     // authorize-id, and give us the authenticate-id,
167                     // which we would store as the authorize-id.  The
168                     // second call will give us the password, which we
169                     // think is the authenticate-id (user).  Then when
170                     // we ask for the password, there are no more
171                     // elements, leading to the exception we just
172                     // caught.  So we need to move the user to the
173                     // password, and the authorize_id to the user.
174                     pass = user;
175                     user = authorize_id;
176                 }
177 
178                 authTokenizer = null;
179             }
180         }
181         catch (Exception e) {
182             // Ignored - this exception in parsing will be dealt
183             // with in the if clause below
184         }
185         // Authenticate user
186         if ((user == null) || (pass == null)) {
187             responseString = "501 Could not decode parameters for AUTH PLAIN";
188             session.writeResponse(responseString);
189         } else if (session.getConfigurationData().getUsersRepository().test(user, pass)) {
190             session.setUser(user);
191             responseString = "235 Authentication Successful";
192             session.writeResponse(responseString);
193             getLogger().info("AUTH method PLAIN succeeded");
194         } else {
195             responseString = "535 Authentication Failed";
196             session.writeResponse(responseString);
197             getLogger().error("AUTH method PLAIN failed");
198         }
199         return;
200     }
201 
202     /**
203      * Carries out the Login AUTH SASL exchange.
204      *
205      * @param session SMTP session object
206      * @param initialResponse the initial response line passed in with the AUTH command
207      */
208     private void doLoginAuth(SMTPSession session, String initialResponse)
209             throws IOException {
210         String user = null, pass = null, responseString = null;
211         if (initialResponse == null) {
212             responseString = "334 VXNlcm5hbWU6"; // base64 encoded "Username:"
213             session.writeResponse(responseString);
214             user = session.readCommandLine();
215         } else {
216             user = initialResponse.trim();
217         }
218         if (user != null) {
219             try {
220                 user = Base64.decodeAsString(user);
221             } catch (Exception e) {
222                 // Ignored - this parse error will be
223                 // addressed in the if clause below
224                 user = null;
225             }
226         }
227         responseString = "334 UGFzc3dvcmQ6"; // base64 encoded "Password:"
228         session.writeResponse(responseString);
229         pass = session.readCommandLine();
230         if (pass != null) {
231             try {
232                 pass = Base64.decodeAsString(pass);
233             } catch (Exception e) {
234                 // Ignored - this parse error will be
235                 // addressed in the if clause below
236                 pass = null;
237             }
238         }
239         // Authenticate user
240         if ((user == null) || (pass == null)) {
241             responseString = "501 Could not decode parameters for AUTH LOGIN";
242         } else if (session.getConfigurationData().getUsersRepository().test(user, pass)) {
243             session.setUser(user);
244             responseString = "235 Authentication Successful";
245             if (getLogger().isDebugEnabled()) {
246                 // TODO: Make this string a more useful debug message
247                 getLogger().debug("AUTH method LOGIN succeeded");
248             }
249         } else {
250             responseString = "535 Authentication Failed";
251             // TODO: Make this string a more useful error message
252             getLogger().error("AUTH method LOGIN failed");
253         }
254         session.writeResponse(responseString);
255         return;
256     }
257 
258     /**
259      * Handles the case of an unrecognized auth type.
260      *
261      * @param session SMTP session object
262      * @param authType the unknown auth type
263      * @param initialResponse the initial response line passed in with the AUTH command
264      */
265     private void doUnknownAuth(SMTPSession session, String authType, String initialResponse) {
266         String responseString = "504 Unrecognized Authentication Type";
267         session.writeResponse(responseString);
268         if (getLogger().isErrorEnabled()) {
269             StringBuffer errorBuffer =
270                 new StringBuffer(128)
271                     .append("AUTH method ")
272                         .append(authType)
273                         .append(" is an unrecognized authentication type");
274             getLogger().error(errorBuffer.toString());
275         }
276         return;
277     }
278 
279 
280 
281     /**
282      * @see org.apache.james.smtpserver.CommandHandler#getImplCommands()
283      */
284     public Collection getImplCommands() {
285         Collection implCommands = new ArrayList();
286         implCommands.add("AUTH");
287         
288         return implCommands;
289     }  
290 }