001    /*
002     * Copyright 2010 the original author or authors.
003     * 
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     *  Unless required by applicable law or agreed to in writing, software
011     *  distributed under the License is distributed on an "AS IS" BASIS,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package com.hs.mail.smtp.processor;
017    
018    import java.io.IOException;
019    import java.util.StringTokenizer;
020    
021    import javax.security.auth.login.LoginException;
022    
023    import com.hs.mail.container.server.socket.TcpTransport;
024    import com.hs.mail.imap.user.UserManager;
025    import com.hs.mail.smtp.SmtpException;
026    import com.hs.mail.smtp.SmtpSession;
027    import com.hs.mail.util.BASE64;
028    
029    /**
030     * Handler for AUTH command.
031     * 
032     * @author Won Chul Doh
033     * @since Jun 27, 2010
034     *
035     */
036    public class AuthProcessor extends AbstractSmtpProcessor {
037    
038            private static final String AUTH_TYPE_PLAIN = "PLAIN";
039            
040            private static final String AUTH_TYPE_LOGIN = "LOGIN";
041            
042            @Override
043            protected void doProcess(SmtpSession session, TcpTransport trans,
044                            StringTokenizer st) throws SmtpException {
045                    try {
046                            doAuth(session, trans, st);
047                    } catch (SmtpException e) {
048                            // re-throw this exception
049                            throw e;
050                    } catch (Exception e) {
051                            // Instead of throwing exception just end the session
052                            trans.endSession();
053                    }
054            }
055    
056            /**
057             * Handles client authentication to the SMTP server. 
058             */
059            private void doAuth(SmtpSession session, TcpTransport trans,
060                            StringTokenizer st) throws Exception {
061                    if (session.getAuthID() > 0) {
062                            throw new SmtpException(SmtpException.ALREADY_AUTHENTICATED);
063                    } else if (st.countTokens() < 1) {
064                            throw new SmtpException(SmtpException.INVALID_COMMAND_PARAM);
065                    }
066                    String authType = nextToken(st);
067                    if (AUTH_TYPE_PLAIN.equalsIgnoreCase(authType)) {
068                            doPlainAuth(session, trans, st);
069                    } else if (AUTH_TYPE_LOGIN.equalsIgnoreCase(authType)) {
070                            doLoginAuth(session, trans, st);
071                    } else {
072                            throw new SmtpException(SmtpException.AUTHTYPE_NOT_SUPPORTED + ": "
073                                            + authType);
074                    }
075            }
076    
077            /**
078             * Handle the Plain AUTH SASL exchange. According to RFC 2595 the client
079             * must send: [authorize-id] \0 authenticate-id \0 password.
080             */
081            private void doPlainAuth(SmtpSession session, TcpTransport trans,
082                            StringTokenizer st) throws IOException {
083                    String userpass = null, user = null, pass = null;
084                    if (st.hasMoreTokens()) {
085                            userpass = nextToken(st);
086                    } else {
087                            session.writeResponse("334 OK. Continue authentication");
088                            userpass = trans.readLine();
089                    }
090                    try {
091                            userpass = new String(BASE64.decode(userpass));
092                            StringTokenizer args = new StringTokenizer(userpass, "\0");
093                            if (args.countTokens() == 3) {
094                                    // RFC 2595 says that "the client may leave the authorization
095                                    // identity empty to indicate that it is the same as the
096                                    // authentication identity.
097                                    // So skip the authorization identity
098                                    args.nextToken();
099                            }
100                            user = args.nextToken();        // Authentication identity
101                            pass = args.nextToken();        // Password
102                    } catch (Exception e) {
103                            // Ignore - this exception will be dealt with in the if clause below
104                    }
105                    // Authenticate user
106                    authenticate(session, trans, user, pass);
107            }
108    
109            /**
110             * Handle the Login AUTH SASL exchange.
111             */
112            private void doLoginAuth(SmtpSession session, TcpTransport trans,
113                            StringTokenizer st) throws IOException {
114                    String user = null, pass = null;
115                    if (st.hasMoreTokens()) {
116                            user = nextToken(st);
117                    } else {
118                            session.writeResponse("334 VXNlcm5hbWU6");      // base64 encoded "Username:"
119                            user = trans.readLine();
120                    }
121                    try {
122                            user = new String(BASE64.decode(user));
123                    } catch (Exception e) {
124                            // Ignore - this exception will be dealt with in the if clause below
125                            user = null;
126                    }
127                    session.writeResponse("334 UGFzc3dvcmQ6"); // base64 encoded "Password:"
128                    pass = trans.readLine();
129                    try {
130                            pass = new String(BASE64.decode(pass));
131                    } catch (Exception e) {
132                            // Ignore - this exception will be dealt with in the if clause below
133                            pass = null;
134                    }
135                    // Authenticate user
136                    authenticate(session, trans, user, pass);
137            }
138            
139            private void authenticate(SmtpSession session, TcpTransport trans,
140                            String user, String pass) {
141                    if ((user == null) || (pass == null)) {
142                            throw new SmtpException(SmtpException.CANNOT_DECODE_PARAM);
143                    } else {
144                            try {
145                                    UserManager manager = getUserManager();
146                                    long authID = manager.login(user, pass);
147                                    session.setAuthID(authID);
148                                    session.writeResponse("235 2.7.0 Authentication Successful");
149                            } catch (LoginException e) {
150                                    throw new SmtpException(SmtpException.AUTH_FAILED);
151                            }
152                    }
153            }
154            
155    }