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.james.util.mail.dsn.DSNStatus;
021: import org.apache.avalon.framework.logger.AbstractLogEnabled;
022: import java.util.Locale;
023: import java.util.StringTokenizer;
024: import org.apache.james.util.Base64;
025: import java.io.IOException;
026:
027: /**
028: * handles AUTH command
029: */
030: public class AuthCmdHandler extends AbstractLogEnabled implements
031: CommandHandler {
032:
033: /**
034: * The text string for the SMTP AUTH type PLAIN.
035: */
036: private final static String AUTH_TYPE_PLAIN = "PLAIN";
037:
038: /**
039: * The text string for the SMTP AUTH type LOGIN.
040: */
041: private final static String AUTH_TYPE_LOGIN = "LOGIN";
042:
043: /**
044: * handles AUTH command
045: *
046: * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
047: */
048: public void onCommand(SMTPSession session) {
049: //deviation from the Main code
050: //Instead of throwing exception just end the session
051: try {
052: doAUTH(session, session.getCommandArgument());
053: } catch (Exception ex) {
054: getLogger().error("Exception occured:" + ex.getMessage());
055: session.endSession();
056: }
057: }
058:
059: /**
060: * Handler method called upon receipt of a AUTH command.
061: * Handles client authentication to the SMTP server.
062: *
063: * @param session SMTP session
064: * @param argument the argument passed in with the command by the SMTP client
065: */
066: private void doAUTH(SMTPSession session, String argument)
067: throws Exception {
068: String responseString = null;
069: if (session.getUser() != null) {
070: responseString = "503 "
071: + DSNStatus.getStatus(DSNStatus.PERMANENT,
072: DSNStatus.DELIVERY_OTHER)
073: + " User has previously authenticated. "
074: + " Further authentication is not required!";
075: session.writeResponse(responseString);
076: } else if (argument == null) {
077: responseString = "501 "
078: + DSNStatus.getStatus(DSNStatus.PERMANENT,
079: DSNStatus.DELIVERY_INVALID_ARG)
080: + " Usage: AUTH (authentication type) <challenge>";
081: session.writeResponse(responseString);
082: } else {
083: String initialResponse = null;
084: if ((argument != null) && (argument.indexOf(" ") > 0)) {
085: initialResponse = argument.substring(argument
086: .indexOf(" ") + 1);
087: argument = argument.substring(0, argument.indexOf(" "));
088: }
089: String authType = argument.toUpperCase(Locale.US);
090: if (authType.equals(AUTH_TYPE_PLAIN)) {
091: doPlainAuth(session, initialResponse);
092: return;
093: } else if (authType.equals(AUTH_TYPE_LOGIN)) {
094: doLoginAuth(session, initialResponse);
095: return;
096: } else {
097: doUnknownAuth(session, authType, initialResponse);
098: return;
099: }
100: }
101: }
102:
103: /**
104: * Carries out the Plain AUTH SASL exchange.
105: *
106: * According to RFC 2595 the client must send: [authorize-id] \0 authenticate-id \0 password.
107: *
108: * >>> AUTH PLAIN dGVzdAB0ZXN0QHdpei5leGFtcGxlLmNvbQB0RXN0NDI=
109: * Decoded: test\000test@wiz.example.com\000tEst42
110: *
111: * >>> AUTH PLAIN dGVzdAB0ZXN0AHRFc3Q0Mg==
112: * Decoded: test\000test\000tEst42
113: *
114: * @param session SMTP session object
115: * @param initialResponse the initial response line passed in with the AUTH command
116: */
117: private void doPlainAuth(SMTPSession session, String initialResponse)
118: throws IOException {
119: String userpass = null, user = null, pass = null, responseString = null;
120: if (initialResponse == null) {
121: responseString = "334 OK. Continue authentication";
122: session.writeResponse(responseString);
123: userpass = session.readCommandLine();
124: } else {
125: userpass = initialResponse.trim();
126: }
127: try {
128: if (userpass != null) {
129: userpass = Base64.decodeAsString(userpass);
130: }
131: if (userpass != null) {
132: /* See: RFC 2595, Section 6
133: The mechanism consists of a single message from the client to the
134: server. The client sends the authorization identity (identity to
135: login as), followed by a US-ASCII NUL character, followed by the
136: authentication identity (identity whose password will be used),
137: followed by a US-ASCII NUL character, followed by the clear-text
138: password. The client may leave the authorization identity empty to
139: indicate that it is the same as the authentication identity.
140:
141: The server will verify the authentication identity and password with
142: the system authentication database and verify that the authentication
143: credentials permit the client to login as the authorization identity.
144: If both steps succeed, the user is logged in.
145: */
146: StringTokenizer authTokenizer = new StringTokenizer(
147: userpass, "\0");
148: String authorize_id = authTokenizer.nextToken(); // Authorization Identity
149: user = authTokenizer.nextToken(); // Authentication Identity
150: try {
151: pass = authTokenizer.nextToken(); // Password
152: } catch (java.util.NoSuchElementException _) {
153: // If we got here, this is what happened. RFC 2595
154: // says that "the client may leave the authorization
155: // identity empty to indicate that it is the same as
156: // the authentication identity." As noted above,
157: // that would be represented as a decoded string of
158: // the form: "\0authenticate-id\0password". The
159: // first call to nextToken will skip the empty
160: // authorize-id, and give us the authenticate-id,
161: // which we would store as the authorize-id. The
162: // second call will give us the password, which we
163: // think is the authenticate-id (user). Then when
164: // we ask for the password, there are no more
165: // elements, leading to the exception we just
166: // caught. So we need to move the user to the
167: // password, and the authorize_id to the user.
168: pass = user;
169: user = authorize_id;
170: }
171:
172: authTokenizer = null;
173: }
174: } catch (Exception e) {
175: // Ignored - this exception in parsing will be dealt
176: // with in the if clause below
177: }
178: // Authenticate user
179: if ((user == null) || (pass == null)) {
180: responseString = "501 Could not decode parameters for AUTH PLAIN";
181: session.writeResponse(responseString);
182: } else if (session.getConfigurationData().getUsersRepository()
183: .test(user, pass)) {
184: session.setUser(user);
185: responseString = "235 Authentication Successful";
186: session.writeResponse(responseString);
187: getLogger().info("AUTH method PLAIN succeeded");
188: } else {
189: responseString = "535 Authentication Failed";
190: session.writeResponse(responseString);
191: getLogger().error("AUTH method PLAIN failed");
192: }
193: return;
194: }
195:
196: /**
197: * Carries out the Login AUTH SASL exchange.
198: *
199: * @param session SMTP session object
200: * @param initialResponse the initial response line passed in with the AUTH command
201: */
202: private void doLoginAuth(SMTPSession session, String initialResponse)
203: throws IOException {
204: String user = null, pass = null, responseString = null;
205: if (initialResponse == null) {
206: responseString = "334 VXNlcm5hbWU6"; // base64 encoded "Username:"
207: session.writeResponse(responseString);
208: user = session.readCommandLine();
209: } else {
210: user = initialResponse.trim();
211: }
212: if (user != null) {
213: try {
214: user = Base64.decodeAsString(user);
215: } catch (Exception e) {
216: // Ignored - this parse error will be
217: // addressed in the if clause below
218: user = null;
219: }
220: }
221: responseString = "334 UGFzc3dvcmQ6"; // base64 encoded "Password:"
222: session.writeResponse(responseString);
223: pass = session.readCommandLine();
224: if (pass != null) {
225: try {
226: pass = Base64.decodeAsString(pass);
227: } catch (Exception e) {
228: // Ignored - this parse error will be
229: // addressed in the if clause below
230: pass = null;
231: }
232: }
233: // Authenticate user
234: if ((user == null) || (pass == null)) {
235: responseString = "501 Could not decode parameters for AUTH LOGIN";
236: } else if (session.getConfigurationData().getUsersRepository()
237: .test(user, pass)) {
238: session.setUser(user);
239: responseString = "235 Authentication Successful";
240: if (getLogger().isDebugEnabled()) {
241: // TODO: Make this string a more useful debug message
242: getLogger().debug("AUTH method LOGIN succeeded");
243: }
244: } else {
245: responseString = "535 Authentication Failed";
246: // TODO: Make this string a more useful error message
247: getLogger().error("AUTH method LOGIN failed");
248: }
249: session.writeResponse(responseString);
250: return;
251: }
252:
253: /**
254: * Handles the case of an unrecognized auth type.
255: *
256: * @param session SMTP session object
257: * @param authType the unknown auth type
258: * @param initialResponse the initial response line passed in with the AUTH command
259: */
260: private void doUnknownAuth(SMTPSession session, String authType,
261: String initialResponse) {
262: String responseString = "504 Unrecognized Authentication Type";
263: session.writeResponse(responseString);
264: if (getLogger().isErrorEnabled()) {
265: StringBuffer errorBuffer = new StringBuffer(128).append(
266: "AUTH method ").append(authType).append(
267: " is an unrecognized authentication type");
268: getLogger().error(errorBuffer.toString());
269: }
270: return;
271: }
272:
273: }
|