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