/* * Tigase Jabber/XMPP Server * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. * * $Rev$ * Last modified by $Author$ * $Date$ */ package tigase.auth; //~--- non-JDK imports -------------------------------------------------------- import tigase.util.Algorithms; import tigase.xmpp.BareJID; //~--- JDK imports ------------------------------------------------------------ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.sasl.AuthorizeCallback; import javax.security.sasl.RealmCallback; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; //~--- classes ---------------------------------------------------------------- /** * Describe class SaslPLAIN here. * * * Created: Mon Nov 6 09:02:31 2006 * * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @version $Rev$ */ public class SaslPLAIN implements SaslServer { /** * Private logger for class instancess. */ private static final Logger log = Logger.getLogger(SaslPLAIN.class.getName()); /** Field description */ public static final String ENCRYPTION_KEY = "password-encryption"; /** Field description */ public static final String ENCRYPTION_PLAIN = "PLAIN"; /** Field description */ public static final String ENCRYPTION_MD5 = "MD5"; /** * This is LibreSource variant of encoding MD5 sum. The calculation is done * the same way but HEX representation of the sum is different: * <pre> * StringBuilder sb = new StringBuilder(); * for (byte b: md5) { * sb.append(Integer.toHexString(b)); * } * </pre> * */ public static final String ENCRYPTION_LS_MD5 = "LS-MD5"; /** Field description */ public static final String ENCRYPTION_SHA = "SHA"; private static final String MECHANISM = "PLAIN"; //~--- fields --------------------------------------------------------------- private CallbackHandler callbackHandler = null; private Map<? super String, ?> props = null; private boolean auth_ok = false; //~--- constructors --------------------------------------------------------- /** * Creates a new <code>SaslPLAIN</code> instance. * */ public SaslPLAIN() {} protected SaslPLAIN(Map<? super String, ?> props, CallbackHandler callbackHandler) { this.props = props; this.callbackHandler = callbackHandler; } //~--- methods -------------------------------------------------------------- /** * Describe <code>dispose</code> method here. * * @exception SaslException if an error occurs */ public void dispose() throws SaslException { props = null; callbackHandler = null; } ///** // * This is not fully correct HEX representation of digest sum but // * this is how Libre Source does it so I have to be compatible with them. // * // * @param passwd a <code>String</code> value // * @return a <code>String</code> value // */ //private String ls_digest(String passwd) throws NoSuchAlgorithmException { // byte[] md5 = Algorithms.digest("", passwd, "MD5"); // StringBuilder sb = new StringBuilder(); // for (byte b: md5) { // sb.append(Integer.toHexString(b)); // } // return sb.toString(); //} /** * Describe <code>evaluateResponse</code> method here. * * @param byteArray a <code>byte[]</code> value * @return a <code>byte[]</code> value * @exception SaslException if an error occurs */ @Override public byte[] evaluateResponse(final byte[] byteArray) throws SaslException { // StringBuilder authz = new StringBuilder(); // fields are separated with \0 char so let's look for the char // position.... int auth_idx = 0; while ((byteArray[auth_idx] != 0) && (auth_idx < byteArray.length)) { ++auth_idx; } String authoriz = new String(byteArray, 0, auth_idx); int user_idx = ++auth_idx; while ((byteArray[user_idx] != 0) && (user_idx < byteArray.length)) { ++user_idx; } String user_id = new String(byteArray, auth_idx, user_idx - auth_idx); if (log.isLoggable(Level.FINEST)) { log.finest("SASL userId: " + user_id); } ++user_idx; String passwd = new String(byteArray, user_idx, byteArray.length - user_idx); if (log.isLoggable(Level.FINEST)) { log.finest("SASL password: " + passwd); } if (passwd != null) { String alg = (String) props.get(ENCRYPTION_KEY); if (alg != null) { try { if (alg.equals(ENCRYPTION_MD5) || alg.equals(ENCRYPTION_SHA)) { passwd = Algorithms.hexDigest("", passwd, alg); if (log.isLoggable(Level.FINEST)) { log.finest("SASL encrypted passwrd: " + passwd); } } // end of if (alg != null && !alg.equals()) // if (alg.equals(ENCRYPTION_LS_MD5)) { // passwd = ls_digest(passwd); // } // end of if (alg != null && !alg.equals()) } catch (NoSuchAlgorithmException e) { throw new SaslException("Password encrypting algorithm is not supported.", e); } // end of try-catch } } // end of if (passwd != null) if (callbackHandler == null) { throw new SaslException("Error: no CallbackHandler available."); } Callback[] callbacks = new Callback[3]; NameCallback nc = new NameCallback("User name", user_id); PasswordCallback pc = new PasswordCallback("User password", false); RealmCallback rc = new RealmCallback("Put domain as realm."); callbacks[0] = nc; callbacks[1] = pc; callbacks[2] = rc; try { callbackHandler.handle(callbacks); char[] real_password = pc.getPassword(); if ( !Arrays.equals(real_password, passwd.toCharArray())) { if (log.isLoggable(Level.FINEST)) { log.finest("SASL passwords missmatch SASL password: " + passwd + ", db password: " + new String(real_password)); } throw new SaslException("Password missmatch."); } if ((authoriz != null) &&!authoriz.isEmpty()) { String realm = rc.getText(); callbacks = new Callback[1]; AuthorizeCallback ac = new AuthorizeCallback(BareJID.toString(user_id, realm), authoriz); callbacks[0] = ac; callbackHandler.handle(callbacks); if (ac.isAuthorized()) { auth_ok = true; } else { throw new SaslException("Not authorized."); } // end of else } else { auth_ok = true; } // end of if (authoriz != null && !authoriz.empty()) else } catch (Exception e) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "SASL authentication error: ", e); } throw new SaslException("Authorization error.", e); } // end of try-catch if (log.isLoggable(Level.FINEST)) { if (auth_ok) { log.finest(user_id + " authentication OK."); } else { log.finest(user_id + " authentication Error."); } } return null; } //~--- get methods ---------------------------------------------------------- /** * Describe <code>getAuthorizationID</code> method here. * * @return a <code>String</code> value */ public String getAuthorizationID() { return null; } /** * Describe <code>getMechanismName</code> method here. * * @return a <code>String</code> value */ public String getMechanismName() { return MECHANISM; } /** * Describe <code>getNegotiatedProperty</code> method here. * * @param string a <code>String</code> value * @return an <code>Object</code> value */ public Object getNegotiatedProperty(final String string) { return null; } /** * Describe <code>isComplete</code> method here. * * @return a <code>boolean</code> value */ public boolean isComplete() { return auth_ok; } //~--- methods -------------------------------------------------------------- // Implementation of javax.security.sasl.SaslServer /** * Describe <code>unwrap</code> method here. * * @param byteArray a <code>byte[]</code> value * @param n an <code>int</code> value * @param n1 an <code>int</code> value * @return a <code>byte[]</code> value * @exception SaslException if an error occurs */ @Override public byte[] unwrap(final byte[] byteArray, final int n, final int n1) throws SaslException { return null; } /** * Describe <code>wrap</code> method here. * * @param byteArray a <code>byte[]</code> value * @param n an <code>int</code> value * @param n1 an <code>int</code> value * @return a <code>byte[]</code> value * @exception SaslException if an error occurs */ public byte[] wrap(final byte[] byteArray, final int n, final int n1) throws SaslException { return null; } } // SaslPLAIN //~ Formatted in Sun Code Convention //~ Formatted by Jindent --- http://www.jindent.com