/* * 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.db.jdbc; //~--- non-JDK imports -------------------------------------------------------- import tigase.auth.SaslPLAIN; import tigase.db.AuthRepository; import tigase.db.AuthorizationException; import tigase.db.DBInitException; import tigase.db.DataRepository; import tigase.db.RepositoryFactory; import tigase.db.TigaseDBException; import tigase.db.UserExistsException; import tigase.db.UserNotFoundException; import tigase.util.Algorithms; import tigase.util.Base64; import tigase.xmpp.BareJID; import static tigase.db.AuthRepository.*; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; import java.math.BigDecimal; import java.security.NoSuchAlgorithmException; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; import java.util.TreeMap; 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.auth.callback.UnsupportedCallbackException; import javax.security.sasl.AuthorizeCallback; import javax.security.sasl.RealmCallback; import javax.security.sasl.Sasl; import javax.security.sasl.SaslException; import javax.security.sasl.SaslServer; //~--- classes ---------------------------------------------------------------- /** * Describe class DrupalWPAuth here. * * * Created: Sat Nov 11 22:22:04 2006 * * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @version $Rev$ */ public class DrupalWPAuth implements AuthRepository { /** * Private logger for class instances. */ private static final Logger log = Logger.getLogger(DrupalWPAuth.class.getName()); private static final String[] non_sasl_mechs = { "password" }; private static final String[] sasl_mechs = { "PLAIN" }; /** Field description */ public static final String DRUPAL_USERS_TBL = "users"; /** Field description */ public static final String DRUPAL_NAME_FLD = "name"; /** Field description */ public static final String DRUPAL_PASS_FLD = "pass"; /** Field description */ public static final String DRUPAL_STATUS_FLD = "status"; /** Field description */ public static final int DRUPAL_OK_STATUS_VAL = 1; /** Field description */ public static final String WP_USERS_TBL = "wp_users"; /** Field description */ public static final String WP_NAME_FLD = "user_login"; /** Field description */ public static final String WP_PASS_FLD = "user_pass"; /** Field description */ public static final String WP_STATUS_FLD = "user_status"; /** Field description */ public static final int WP_OK_STATUS_VAL = 0; private static final String SELECT_PASSWORD_QUERY_KEY = "select-password-drupal-wp-query-key"; private static final String SELECT_STATUS_QUERY_KEY = "select-status-drupal-wp-query-key"; private static final String INSERT_USER_QUERY_KEY = "insert-user-drupal-wp-query-key"; private static final String UPDATE_LAST_LOGIN_QUERY_KEY = "update-last-login-drupal-wp-query-key"; private static final String UPDATE_ONLINE_STATUS_QUERY_KEY = "update-online-status-drupal-wp-query-key"; //~--- fields --------------------------------------------------------------- private DataRepository data_repo = null; private String name_fld = DRUPAL_NAME_FLD; private String users_tbl = DRUPAL_USERS_TBL; private int status_val = DRUPAL_OK_STATUS_VAL; private String status_fld = DRUPAL_STATUS_FLD; private String pass_fld = DRUPAL_PASS_FLD; private boolean online_status = false; private boolean last_login = true; //~--- methods -------------------------------------------------------------- /** * Describe <code>addUser</code> method here. * * @param user a <code>String</code> value * @param password a <code>String</code> value * @exception UserExistsException if an error occurs * @exception TigaseDBException if an error occurs */ @Override public void addUser(BareJID user, final String password) throws UserExistsException, TigaseDBException { try { PreparedStatement user_add_st = data_repo.getPreparedStatement(user, INSERT_USER_QUERY_KEY); synchronized (user_add_st) { user_add_st.setString(1, user.getLocalpart()); user_add_st.setString(2, Algorithms.hexDigest("", password, "MD5")); user_add_st.executeUpdate(); } } catch (NoSuchAlgorithmException e) { throw new TigaseDBException("Password encoding algorithm is not supported.", e); } catch (SQLException e) { throw new UserExistsException("Error while adding user to repository, user exists?", e); } } /** * Describe <code>digestAuth</code> method here. * * @param user a <code>String</code> value * @param digest a <code>String</code> value * @param id a <code>String</code> value * @param alg a <code>String</code> value * @return a <code>boolean</code> value * @exception UserNotFoundException if an error occurs * @exception TigaseDBException if an error occurs * @exception AuthorizationException if an error occurs */ @Override @Deprecated public boolean digestAuth(BareJID user, final String digest, final String id, final String alg) throws UserNotFoundException, TigaseDBException, AuthorizationException { throw new AuthorizationException("Not supported."); } //~--- get methods ---------------------------------------------------------- /** * Method description * * * @return */ @Override public String getResourceUri() { return data_repo.getResourceUri(); } /** * Method description * * * @return */ @Override public long getUsersCount() { return -1; } /** * Method description * * * @param domain * * @return */ @Override public long getUsersCount(String domain) { return -1; } //~--- methods -------------------------------------------------------------- /** * Describe <code>initRepository</code> method here. * * @param connection_str a <code>String</code> value * @param params * @exception DBInitException if an error occurs */ @Override public void initRepository(final String connection_str, Map<String, String> params) throws DBInitException { try { data_repo = RepositoryFactory.getDataRepository(null, connection_str, params); if (connection_str.contains("online_status=true")) { online_status = true; } if (connection_str.contains("wp_mode=true")) { online_status = false; last_login = false; name_fld = WP_NAME_FLD; users_tbl = WP_USERS_TBL; status_val = WP_OK_STATUS_VAL; status_fld = WP_STATUS_FLD; pass_fld = WP_PASS_FLD; log.log(Level.INFO, "Initializing Wordpress repository: {0}", connection_str); } else { log.log(Level.INFO, "Initializing Drupal repository: {0}", connection_str); } String query = "select " + pass_fld + " from " + users_tbl + " where " + name_fld + " = ?"; data_repo.initPreparedStatement(SELECT_PASSWORD_QUERY_KEY, query); query = "select " + status_fld + " from " + users_tbl + " where " + name_fld + " = ?"; data_repo.initPreparedStatement(SELECT_STATUS_QUERY_KEY, query); query = "insert into " + users_tbl + " (" + name_fld + ", " + pass_fld + ", " + status_fld + ")" + " values (?, ?, " + status_val + ")"; data_repo.initPreparedStatement(INSERT_USER_QUERY_KEY, query); query = "update " + users_tbl + " set access=?, login=? where " + name_fld + " = ?"; data_repo.initPreparedStatement(UPDATE_LAST_LOGIN_QUERY_KEY, query); query = "update " + users_tbl + " set online_status=online_status+? where " + name_fld + " = ?"; data_repo.initPreparedStatement(UPDATE_ONLINE_STATUS_QUERY_KEY, query); } catch (Exception e) { data_repo = null; throw new DBInitException("Problem initializing jdbc connection: " + connection_str, e); } try { if (online_status) { Statement stmt = data_repo.createStatement(null); stmt.executeUpdate("update users set online_status = 0;"); stmt.close(); stmt = null; } } catch (SQLException e) { if (e.getMessage().contains("'online_status'")) { try { Statement stmt = data_repo.createStatement(null); stmt.executeUpdate("alter table users add online_status int default 0;"); stmt.close(); stmt = null; } catch (SQLException ex) { data_repo = null; throw new DBInitException("Problem initializing jdbc connection: " + connection_str, ex); } } else { data_repo = null; throw new DBInitException("Problem initializing jdbc connection: " + connection_str, e); } } } /** * Method description * * * @param user * * @throws TigaseDBException * @throws UserNotFoundException */ @Override public void logout(BareJID user) throws UserNotFoundException, TigaseDBException { updateOnlineStatus(user, -1); } /** * Describe <code>otherAuth</code> method here. * * @param props a <code>Map</code> value * @return a <code>boolean</code> value * @exception UserNotFoundException if an error occurs * @exception TigaseDBException if an error occurs * @exception AuthorizationException if an error occurs */ @Override public boolean otherAuth(final Map<String, Object> props) throws UserNotFoundException, TigaseDBException, AuthorizationException { String proto = (String) props.get(PROTOCOL_KEY); if (proto.equals(PROTOCOL_VAL_SASL)) { String mech = (String) props.get(MACHANISM_KEY); try { if (mech.equals("PLAIN")) { boolean login_ok = saslAuth(props); if (login_ok) { BareJID user = (BareJID) props.get(USER_ID_KEY); // Unfortunately, unlike with plainAuth we have to check whether the user // is active after successful authentication as before it is completed the // user id is not known if ( !isActive(user)) { throw new AuthorizationException("User account has been blocked."); } // end of if (!isActive(user)) updateLastLogin(user); updateOnlineStatus(user, 1); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "User authenticated: {0}", user); } } else { if (log.isLoggable(Level.FINEST)) { log.finest("User NOT authenticated"); } } return login_ok; } // end of if (mech.equals("PLAIN")) } catch (Exception e) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "OTHER authentication error: ", e); } throw new AuthorizationException("Sasl exception.", e); } // end of try-catch throw new AuthorizationException("Mechanism is not supported: " + mech); } // end of if (proto.equals(PROTOCOL_VAL_SASL)) if (proto.equals(PROTOCOL_VAL_NONSASL)) { String password = (String) props.get(PASSWORD_KEY); BareJID user_id = (BareJID) props.get(USER_ID_KEY); if (password != null) { return plainAuth(user_id, password); } String digest = (String) props.get(DIGEST_KEY); if (digest != null) { String digest_id = (String) props.get(DIGEST_ID_KEY); return digestAuth(user_id, digest, digest_id, "SHA"); } } // end of if (proto.equals(PROTOCOL_VAL_SASL)) throw new AuthorizationException("Protocol is not supported: " + proto); } /** * Describe <code>plainAuth</code> method here. * * @param user a <code>String</code> value * @param password a <code>String</code> value * @return a <code>boolean</code> value * * @throws AuthorizationException * @exception UserNotFoundException if an error occurs * @exception TigaseDBException if an error occurs */ @Override @Deprecated public boolean plainAuth(BareJID user, final String password) throws UserNotFoundException, TigaseDBException, AuthorizationException { try { if ( !isActive(user)) { throw new AuthorizationException("User account has been blocked."); } // end of if (!isActive(user)) String enc_passwd = Algorithms.hexDigest("", password, "MD5"); String db_password = getPassword(user); boolean login_ok = db_password.equals(enc_passwd); if (login_ok) { updateLastLogin(user); updateOnlineStatus(user, 1); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "User authenticated: {0}", user); } } else { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "User NOT authenticated: {0}", user); } } return login_ok; } catch (NoSuchAlgorithmException e) { throw new AuthorizationException("Password encoding algorithm is not supported.", e); } catch (SQLException e) { throw new TigaseDBException("Problem accessing repository.", e); } // end of catch } // Implementation of tigase.db.AuthRepository /** * Describe <code>queryAuth</code> method here. * * @param authProps a <code>Map</code> value */ @Override public void queryAuth(final Map<String, Object> authProps) { String protocol = (String) authProps.get(PROTOCOL_KEY); if (protocol.equals(PROTOCOL_VAL_NONSASL)) { authProps.put(RESULT_KEY, non_sasl_mechs); } // end of if (protocol.equals(PROTOCOL_VAL_NONSASL)) if (protocol.equals(PROTOCOL_VAL_SASL)) { authProps.put(RESULT_KEY, sasl_mechs); } // end of if (protocol.equals(PROTOCOL_VAL_NONSASL)) } /** * Describe <code>removeUser</code> method here. * * @param user a <code>String</code> value * @exception UserNotFoundException if an error occurs * @exception TigaseDBException if an error occurs */ @Override public void removeUser(BareJID user) throws UserNotFoundException, TigaseDBException { throw new TigaseDBException("Removing user is not supported."); } /** * Describe <code>updatePassword</code> method here. * * @param user a <code>String</code> value * @param password a <code>String</code> value * @exception TigaseDBException if an error occurs * @throws UserNotFoundException */ @Override public void updatePassword(BareJID user, final String password) throws UserNotFoundException, TigaseDBException { throw new TigaseDBException("Updating user password is not supported."); } //~--- get methods ---------------------------------------------------------- //private long getMaxUID() throws SQLException { // ResultSet rs = null; // // try { // synchronized (max_uid_st) { // rs = max_uid_st.executeQuery(); // // if (rs.next()) { // BigDecimal max_uid = rs.getBigDecimal(1); // // // System.out.println("MAX UID = " + max_uid.longValue()); // return max_uid.longValue(); // } else { // // // System.out.println("MAX UID = -1!!!!"); // return -1; // } // end of else // } // } finally { // release(null, rs); // } //} private String getPassword(BareJID user) throws SQLException, UserNotFoundException { ResultSet rs = null; try { PreparedStatement pass_st = data_repo.getPreparedStatement(user, SELECT_PASSWORD_QUERY_KEY); synchronized (pass_st) { pass_st.setString(1, user.getLocalpart()); rs = pass_st.executeQuery(); if (rs.next()) { return rs.getString(1); } else { throw new UserNotFoundException("User does not exist: " + user); } // end of if (isnext) else } } finally { data_repo.release(null, rs); } } private boolean isActive(BareJID user) throws SQLException, UserNotFoundException { ResultSet rs = null; try { PreparedStatement status_st = data_repo.getPreparedStatement(user, SELECT_STATUS_QUERY_KEY); synchronized (status_st) { status_st.setString(1, user.getLocalpart()); rs = status_st.executeQuery(); if (rs.next()) { return (rs.getInt(1) == status_val); } else { throw new UserNotFoundException("User does not exist: " + user); } // end of if (isnext) else } } finally { data_repo.release(null, rs); } } //~--- methods -------------------------------------------------------------- private boolean saslAuth(final Map<String, Object> props) throws AuthorizationException { try { SaslServer ss = (SaslServer) props.get("SaslServer"); if (ss == null) { Map<String, String> sasl_props = new TreeMap<String, String>(); sasl_props.put(Sasl.QOP, "auth"); sasl_props.put(SaslPLAIN.ENCRYPTION_KEY, SaslPLAIN.ENCRYPTION_MD5); ss = Sasl.createSaslServer((String) props.get(MACHANISM_KEY), "xmpp", (String) props.get(SERVER_NAME_KEY), sasl_props, new SaslCallbackHandler(props)); props.put("SaslServer", ss); } // end of if (ss == null) String data_str = (String) props.get(DATA_KEY); byte[] in_data = ((data_str != null) ? Base64.decode(data_str) : new byte[0]); byte[] challenge = ss.evaluateResponse(in_data); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "challenge: {0}", ((challenge != null) ? new String(challenge) : "null")); } String challenge_str = (((challenge != null) && (challenge.length > 0)) ? Base64.encode(challenge) : null); props.put(RESULT_KEY, challenge_str); if (ss.isComplete()) { return true; } else { return false; } // end of if (ss.isComplete()) else } catch (SaslException e) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "SASL authentication error: ", e); } throw new AuthorizationException("Sasl exception.", e); } catch (Exception e) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "SASL authentication error: ", e); } throw new AuthorizationException("Sasl exception.", e); } // end of try-catch } private void updateLastLogin(BareJID user) throws TigaseDBException { if (last_login) { try { PreparedStatement update_last_login_st = data_repo.getPreparedStatement(user, UPDATE_LAST_LOGIN_QUERY_KEY); synchronized (update_last_login_st) { BigDecimal bd = new BigDecimal((System.currentTimeMillis() / 1000)); update_last_login_st.setBigDecimal(1, bd); update_last_login_st.setBigDecimal(2, bd); update_last_login_st.setString(3, user.getLocalpart()); update_last_login_st.executeUpdate(); } } catch (SQLException e) { throw new TigaseDBException("Error accessing repository.", e); } // end of try-catch } } private void updateOnlineStatus(BareJID user, int status) throws TigaseDBException { if (online_status) { try { PreparedStatement update_online_status = data_repo.getPreparedStatement(user, UPDATE_ONLINE_STATUS_QUERY_KEY); synchronized (update_online_status) { update_online_status.setInt(1, status); update_online_status.setString(2, user.getLocalpart()); update_online_status.executeUpdate(); } } catch (SQLException e) { throw new TigaseDBException("Error accessing repository.", e); } // end of try-catch } } //~--- inner classes -------------------------------------------------------- private class SaslCallbackHandler implements CallbackHandler { private Map<String, Object> options = null; //~--- constructors ------------------------------------------------------- private SaslCallbackHandler(final Map<String, Object> options) { this.options = options; } //~--- methods ------------------------------------------------------------ // Implementation of javax.security.auth.callback.CallbackHandler /** * Describe <code>handle</code> method here. * * @param callbacks a <code>Callback[]</code> value * @exception IOException if an error occurs * @exception UnsupportedCallbackException if an error occurs */ @Override public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { BareJID jid = null; for (int i = 0; i < callbacks.length; i++) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Callback: {0}", callbacks[i].getClass().getSimpleName()); } if (callbacks[i] instanceof RealmCallback) { RealmCallback rc = (RealmCallback) callbacks[i]; String realm = (String) options.get(REALM_KEY); if (realm != null) { rc.setText(realm); } // end of if (realm == null) if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "RealmCallback: {0}", realm); } } else { if (callbacks[i] instanceof NameCallback) { NameCallback nc = (NameCallback) callbacks[i]; String user_name = nc.getName(); if (user_name == null) { user_name = nc.getDefaultName(); } // end of if (name == null) jid = BareJID.bareJIDInstanceNS(user_name, (String) options.get(REALM_KEY)); options.put(USER_ID_KEY, jid); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "NameCallback: {0}", user_name); } } else { if (callbacks[i] instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback) callbacks[i]; try { String passwd = getPassword(jid); pc.setPassword(passwd.toCharArray()); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "PasswordCallback: {0}", passwd); } } catch (Exception e) { throw new IOException("Password retrieving problem.", e); } // end of try-catch } else { if (callbacks[i] instanceof AuthorizeCallback) { AuthorizeCallback authCallback = ((AuthorizeCallback) callbacks[i]); String authenId = authCallback.getAuthenticationID(); String authorId = authCallback.getAuthorizationID(); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "AuthorizeCallback: authenId: {0}", authenId); log.log(Level.FINEST, "AuthorizeCallback: authorId: {0}", authorId); } // if (authenId.equals(authorId)) { authCallback.setAuthorized(true); // } // end of if (authenId.equals(authorId)) } else { throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback"); } } } } } } } } // DrupalWPAuth //~ Formatted in Sun Code Convention //~ Formatted by Jindent --- http://www.jindent.com