package org.subethamail.smtp.auth; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import org.subethamail.smtp.AuthenticationHandler; import org.subethamail.smtp.AuthenticationHandlerFactory; import org.subethamail.smtp.RejectException; import org.subethamail.smtp.util.Base64; import org.subethamail.smtp.util.TextUtils; /** * Implements the SMTP AUTH LOGIN mechanism.<br> * You are only required to plug your UsernamePasswordValidator implementation * for username and password validation to take effect. * <p> * LOGIN is an obsolete authentication method which has no formal specification. * There is an expired IETF draft for informational purposes. A Microsoft * document can also be found, which intends to specify the LOGIN mechanism. The * latter is not entirely compatible, neither with the IETF draft nor with RFC * 4954 (SMTP Service Extension for Authentication). However this implementation * is likely usable with clients following any of the two documents. * * @see <a href="http://tools.ietf.org/html/draft-murchison-sasl-login-00">The * LOGIN SASL Mechanism</a> * @see <a * href="http://download.microsoft.com/download/5/d/d/5dd33fdf-91f5-496d-9884-0a0b0ee698bb/%5BMS-XLOGIN%5D.pdf">[MS-XLOGIN]</a> * * @author Marco Trevisan <mrctrevisan@yahoo.it> * @author Jeff Schnitzer */ public class LoginAuthenticationHandlerFactory implements AuthenticationHandlerFactory { static List<String> MECHANISMS = new ArrayList<String>(1); static { MECHANISMS.add("LOGIN"); } private UsernamePasswordValidator helper; /** */ public LoginAuthenticationHandlerFactory(UsernamePasswordValidator helper) { this.helper = helper; } /** */ public List<String> getAuthenticationMechanisms() { return MECHANISMS; } /** */ public AuthenticationHandler create() { return new Handler(); } /** */ class Handler implements AuthenticationHandler { private String username; private String password; @Override public String auth(String clientInput) throws RejectException { StringTokenizer stk = new StringTokenizer(clientInput); String token = stk.nextToken(); if (token.trim().equalsIgnoreCase("AUTH")) { if (!stk.nextToken().trim().equalsIgnoreCase("LOGIN")) { // Mechanism mismatch throw new RejectException(504, "AUTH mechanism mismatch"); } if (stk.hasMoreTokens()) { // The client submitted an initial response, which should be // the username. // .Net's built in System.Net.Mail.SmtpClient sends its // authentication this way (and this way only). byte[] decoded = Base64.decode(stk.nextToken()); if (decoded == null) throw new RejectException(501, /*5.5.4*/ "Invalid command argument, not a valid Base64 string"); username = TextUtils.getStringUtf8(decoded); return "334 " + Base64.encodeToString( TextUtils.getAsciiBytes("Password:"), false); } else { return "334 " + Base64.encodeToString( TextUtils.getAsciiBytes("Username:"), false); } } if (this.username == null) { byte[] decoded = Base64.decode(clientInput); if (decoded == null) { throw new RejectException(501, /*5.5.4*/ "Invalid command argument, not a valid Base64 string"); } this.username = TextUtils.getStringUtf8(decoded); return "334 " + Base64.encodeToString( TextUtils.getAsciiBytes("Password:"), false); } byte[] decoded = Base64.decode(clientInput); if (decoded == null) { throw new RejectException(501, /*5.5.4*/ "Invalid command argument, not a valid Base64 string"); } this.password = TextUtils.getStringUtf8(decoded); try { LoginAuthenticationHandlerFactory.this.helper.login(this.username, this.password); } catch (LoginFailedException lfe) { throw new RejectException(535, /*5.7.8*/ "Authentication credentials invalid"); } return null; } /* */ public Object getIdentity() { return this.username; } } }